<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>文心修舍</title>
    <description>曾经是大厂架构师，互联网公司CTO，现在现在致力于学习和传播人工智能、软件工程、技术管理以及其他方面感兴趣的知识</description>
    <link>https://www.yuxiumin.com/</link>
    <atom:link href="https://www.yuxiumin.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Wed, 27 May 2026 20:05:14 +0800</pubDate>
    <lastBuildDate>Wed, 27 May 2026 20:05:14 +0800</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>REST API 设计指南</title>
        <description>&lt;p&gt;REST(Representational State Transfer) 是在 2000 年由 Roy Fielding 起草的 web services 设计方法。虽然 REST 本身是协议无关的，但是长久以来, 一直用 HTTP 协议作为底层协议使用。&lt;/p&gt;

&lt;p&gt;作为使用 HTTP 协议的 RESTful API，主要遵循以下几个设计原则:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;REST API 主要围绕资源进行设计，包含任意类型的对象、数据、或者可以被客户端访问的服务;&lt;/li&gt;
  &lt;li&gt;每一个资源通常有一个资源标识符，唯一的标识了资源, 例如: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://example.xueyufish.com/users/1&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;客户端通过交换资源来表示与服务端的交互，例如，大多数应用使用 JSON 作为交换格式，也有通过 XML 或者 Protobuf 进行交互;&lt;/li&gt;
  &lt;li&gt;REST API 使用统一接口帮助解耦客户端和服务端实现，最通用的操作包括 GET, POST, PUT, PATCH, 和 DELETE.&lt;/li&gt;
  &lt;li&gt;REST API 使用无状态请求模型。HTTP 请求应该是独立并且可能以任何顺序发生的，所以不能在请求之间保留瞬时状态信息，唯一可以存储信息的地方是资源本身，每个请求都应该是一个原子操作。&lt;/li&gt;
  &lt;li&gt;REST API 通过资源中的超文本链接来进行驱动.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;围绕资源来组织-api&quot;&gt;围绕资源来组织 API&lt;/h1&gt;
&lt;p&gt;REST API 通常围绕资源来组织，在业务系统中，从架构设计出发，通常会聚焦在业务实体或者领域对象的聚合上。我们在组织资源的 URI 时，应当尽可能的使用资源本身的名词而非基于资源操作的动词。例如，对于订单创建的操作：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST https://example.xueyufish.com/orders //  (1) Good

POST https://example.xueyufish.com/create-order // (2) Bad
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;请求 (1) 使用 POST 方式创建订单(基于订单资源本身的名词)，请求 (2) 基于动词，更加推荐使用第一种。在复杂业务场景中，API 和下游数据库的实体往往不是一一对应的，更好的做法应该是和 DDD 中领域对象的聚合相对应。&lt;/p&gt;

&lt;p&gt;此外，推荐在 API 中采用统一风格的命名习惯，保留一致性的结构层次。例如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET https://example.xueyufish.com/orders    // (3)

GET https://example.xueyufish.com/orders/{id}   // (4)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;请求 (3) 查询所有的订单列表，请求 (4) 查询具体 id 的订单信息。在 URI 中，通常推荐复数形式。&lt;/p&gt;

&lt;p&gt;也可以使用类似的层级关系来表达更深层的逻辑含义，例如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET https://example.xueyufish.com/orders/1/customers    // (5)

GET https://example.xueyufish.com/customers/10/orders   // (6)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;请求 (5) 查询订单 1 下所关联的所有客户信息，而请求 (6) 则查询客户 10 下所关联的所有订单信息。&lt;/p&gt;

&lt;p&gt;对于一些非 CRUD 的操作，也可以采用类似的方法，例如 Github API 中的 (&lt;a href=&quot;http://developer.github.com/v3/gists/#star-a-gist&quot;&gt;star a gist&lt;/a&gt;) 和 (&lt;a href=&quot;http://developer.github.com/v3/gists/#unstar-a-gist&quot;&gt;unstar&lt;/a&gt;) 分别执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /gists/:id/star&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /gists/:gist_id/star&lt;/code&gt; 操作。&lt;/p&gt;

&lt;h1 id=&quot;根据-http-方法定义操作&quot;&gt;根据 HTTP 方法定义操作&lt;/h1&gt;
&lt;p&gt;HTTP 协议定义了为请求分配语义级别含义的方法，大多数 RESTful API 使用的常见 HTTP 方法包括：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GET: 用于从指定的 URI 接收资源.&lt;/li&gt;
  &lt;li&gt;POST: 用于从指定的 URI 创建资源或触发某种操作.&lt;/li&gt;
  &lt;li&gt;PUT: 用于从指定的 URI 创建或者更新资源&lt;/li&gt;
  &lt;li&gt;PATCH: 用于从指定的 URI 对资源进行部分更新&lt;/li&gt;
  &lt;li&gt;DELETE: 用于从指定的 URI 对资源移除&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以订单和客户举例如下:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Resource&lt;/th&gt;
      &lt;th&gt;POST&lt;/th&gt;
      &lt;th&gt;GET&lt;/th&gt;
      &lt;th&gt;PUT&lt;/th&gt;
      &lt;th&gt;PATCH&lt;/th&gt;
      &lt;th&gt;DELETE&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;/customers&lt;/td&gt;
      &lt;td&gt;创建新客户&lt;/td&gt;
      &lt;td&gt;获取所有客户&lt;/td&gt;
      &lt;td&gt;批量更新客户&lt;/td&gt;
      &lt;td&gt;删除所有客户&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/customers/1&lt;/td&gt;
      &lt;td&gt;–&lt;/td&gt;
      &lt;td&gt;获取客户 1 详情&lt;/td&gt;
      &lt;td&gt;如果客户 1 存在则更新&lt;/td&gt;
      &lt;td&gt;删除客户 1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/customers/1/orders&lt;/td&gt;
      &lt;td&gt;为客户 1 创建新订单&lt;/td&gt;
      &lt;td&gt;获取客户 1 的所有订单&lt;/td&gt;
      &lt;td&gt;批量更新客户 1 的所有订单&lt;/td&gt;
      &lt;td&gt;删除客户 1 的所有订单&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其中 POST、PUT 和 PATCH 常常会被混淆，主要区别如下:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;POST 主要被用来向服务申请创建资源。在 REST 中，主要被应用于集合类型，也可以被用做提交数据给服务做处理。&lt;/li&gt;
  &lt;li&gt;PUT 用于更新资源。请求体中包含资源的完整描述，在 REST 中，更多被应用于独立的元素。服务端判断如果请求的资源已经存在，就用新的资源取代它，否则创建一个新的资源(具体是否创建依赖于服务实现)。&lt;/li&gt;
  &lt;li&gt;PATCH 用来对资源进行部分更新操作，请求体中通常只包含需要变更的部分内容。如果服务实现需要支持，请求的资源如果不存在 PATCH 也可以创建新的资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;遵循-http-语义实现&quot;&gt;遵循 HTTP 语义实现&lt;/h1&gt;
&lt;p&gt;设计 REST API 时，应当尽可能遵循 HTTP 语义，以下列出了几点需要注意的：&lt;/p&gt;

&lt;h2 id=&quot;media-types&quot;&gt;Media types&lt;/h2&gt;
&lt;p&gt;在 HTTP 协议中，API 消费者和服务通过请求和响应来交换资源，而交互格式通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MIME types&lt;/code&gt; 来定义。例如，对于 JSON 格式使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;media type = application/json&lt;/code&gt;, 对于 XML 格式使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;media type = application/xml&lt;/code&gt;。请求和响应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type&lt;/code&gt; 头包含了具体的资源格式，例如以下 POST 请求包含 JSON 格式数据：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST https://example.xueyufish.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57

{&quot;Id&quot;:1,&quot;Name&quot;:&quot;xueyufish&quot;,&quot;Category&quot;:&quot;Widgets&quot;,&quot;Price&quot;:1.99}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果服务端不支持指定的媒体类型，将返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;415(Unsupported Media Type)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;客户端也可以在请求头中包含一个 Accept 字段指定期望从服务端接收的媒体类型的列表，例如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET https://example.xueyufish.com/orders HTTP/1.1
Accept: application/json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果服务端不能匹配任何媒体类型，将返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;406(Not Acceptable)&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;get-方法&quot;&gt;GET 方法&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;查询成功: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200(OK)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;不能找到指定资源: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;404(Not Found)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;post-方法&quot;&gt;POST 方法&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;成功创建新资源: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;201(Created)&lt;/code&gt;。新创建资源的 URI 将包含在响应头的 Location 字段中，响应体包含新创建资源的内容；&lt;/li&gt;
  &lt;li&gt;进行其他处理操作: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200(OK)&lt;/code&gt;, 并将操作结果包含在响应体中一起返回；如果操作没有返回结果，返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;204(No Content)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;请求体包含无效数据: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400(Bad Request)&lt;/code&gt;, 响应体包含额外的错误信息或者 URI。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;put-方法&quot;&gt;PUT 方法&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;成功创建新资源: 和 POST 类似，返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;201(Created)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;成功更新资源: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200(OK)&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;204 (No Content)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;patch-方法&quot;&gt;PATCH 方法&lt;/h2&gt;
&lt;p&gt;PATCH 方法规范(&lt;a href=&quot;https://tools.ietf.org/html/rfc5789&quot;&gt;RFC 5789&lt;/a&gt;)对于 PATCH 方法的格式没有做出特殊定义，具体格式需要依赖于实现的媒体类型。对于 JSON 格式，可分为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON patch&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON merge patch&lt;/code&gt; 两种。&lt;/p&gt;

&lt;p&gt;JSON merge patch 从结构上相对简单。PATCH 方法的格式和原始的 JSON 资源格式类似，内容上为原始 JSON 的子集，包含需要增加或修改的内容，对于需要删除的内容标记为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;。例如，如下：&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gizmo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;widgets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;color&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;blue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;price&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;对应的 PATCH 方法资源为：&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;price&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;color&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;small&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这个 PATCH 方法告诉服务修改 price 字段，删除 color 字段，并且增加 size 字段。对于 JSON merge patch, 媒体类型为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/merge-patch+json&lt;/code&gt;，详情可见 &lt;a href=&quot;https://tools.ietf.org/html/rfc7396&quot;&gt;RFC 7396&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;对于 JSON merge patch 来说，缺点在于原始 JSON 文档中不能包含 null 值，并且无法指定 update 操作的顺序，而 JSON patch 提供了更加灵活的方式，详情可参见 &lt;a href=&quot;https://tools.ietf.org/html/rfc6902&quot;&gt;RFC 6902&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;delete-方法&quot;&gt;DELETE 方法&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;成功删除资源: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;204(No Content)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;资源不存在: 返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;404(Not Found)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;异步操作&quot;&gt;异步操作&lt;/h2&gt;
&lt;p&gt;有时请求发送给服务端以后，服务端需要一段较长时间来进行处理，此时如果客户端一直等待响应消息，会导致延迟非常长。所以，比较理想的选择是将消息转化为异步处理。&lt;/p&gt;

&lt;p&gt;进行异步操作时，服务端对于异步请求会响应 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;202 (Accepted)&lt;/code&gt; 表示当前请求已经被接收处理但是尚未完成。&lt;/p&gt;

&lt;p&gt;服务端可以提供一个状态接口返回异步请求的状态，客户端通过轮询此接口来查询当前异步请求状态情况。通常状态接口的地址会随着 HTTP 状态码 202 一起返回，例如：&lt;/p&gt;
&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;202&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Accepted&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/api/status/12345&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果客户端后续发送一个 GET 请求至状态接口，状态接口响应中应当包含异步操作的当前状态，也可以包含预计完成时间和取消操作的链接地址，例如：&lt;/p&gt;
&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;OK&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;In progress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cancel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/api/status/12345&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果异步操作创建了一个新的资源，状态接口应当返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;303(See Other)&lt;/code&gt;，同时在 Location 头中包含新资源的 URI，例如：&lt;/p&gt;
&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;303&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;See Other&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/api/orders/12345&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;结果筛选排序和搜索&quot;&gt;结果筛选、排序和搜索&lt;/h1&gt;

&lt;p&gt;在 API 设计过程中应当尽可能的保证原始基准 URI 的简单清晰。对于复杂的筛选、排序和搜索请求，可以通过在查询字符串中添加条件来实现：&lt;/p&gt;

&lt;h2 id=&quot;筛选&quot;&gt;筛选&lt;/h2&gt;
&lt;p&gt;对实现筛选的每个字段使用唯一的查询参数。&lt;/p&gt;

&lt;p&gt;例如，当从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/orders&lt;/code&gt; 接口请求订单列表时，可能希望将其限制为仅查询状态为已完成，这时可以通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /orders?status=finished&lt;/code&gt; 之类的请求来完成。这里，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; 是一个实现过滤器的查询参数。&lt;/p&gt;

&lt;h2 id=&quot;排序&quot;&gt;排序&lt;/h2&gt;
&lt;p&gt;类似于筛选，排序可以通过参数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sort&lt;/code&gt; 来描述。&lt;/p&gt;

&lt;p&gt;sort 参数可以接受逗号分隔的字段列表来适应复杂的排序要求，每个字段都可能有一元负数，以表示降序排序顺序，例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customers?sort=-age&lt;/code&gt;, 表示按客户的年龄降序排列&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customers?sort=-age, name&lt;/code&gt;, 表示按客户的年龄降序排列, 按姓名升序排列&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;搜索&quot;&gt;搜索&lt;/h2&gt;
&lt;p&gt;当使用类似 Elasticsearch 之类的全文搜索进行搜索时，通常在查询字符串中增加参数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q&lt;/code&gt; 以表示为字段为全文搜索类型，字段将被传递给搜索引擎处理&lt;/p&gt;

&lt;h2 id=&quot;限制字段返回&quot;&gt;限制字段返回&lt;/h2&gt;
&lt;p&gt;有时 API 的客户端消费者并不总是需要资源的完整表示，选择返回部分字段的能力可以在很大程度上使 API 使用者最小化网络流量。&lt;/p&gt;

&lt;p&gt;可以在查询字符串中添加接受逗号分隔的字段列表的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fields&lt;/code&gt; 参数来达到这个目的。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /customers?fields=id,name,age,updated_at&amp;amp;state=open&amp;amp;sort=-updated_at
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务端接收到上述请求后，将只返回 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id,name,age,updated_at&lt;/code&gt; 字段。&lt;/p&gt;

&lt;h1 id=&quot;版本化&quot;&gt;版本化&lt;/h1&gt;
&lt;p&gt;给 RESTful APIs 添加版本主要有以下几种方式：&lt;/p&gt;

&lt;h2 id=&quot;uri-版本&quot;&gt;URI 版本&lt;/h2&gt;
&lt;p&gt;URI 版本的方式直接在请求 URI 中添加版本号，是相对简单的方式，但是依赖服务端路由请求到指定的 endpoint。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://example.xueyufish.com/v2/customers/3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，当 Web API 经过多次迭代时，它可能变得难以操作，并且服务器必须支持许多不同的版本。同时这个方案还会使得 HATEOAS 的实现复杂化，因为所有链接都需要包含版本号。&lt;/p&gt;

&lt;h2 id=&quot;查询字符串版本&quot;&gt;查询字符串版本&lt;/h2&gt;
&lt;p&gt;查询字符串版本的方式就是将版本号放在 URI 的查询字符串中，如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://example.xueyufish.com/customers/3?version=2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这种方法具有语义上的优势，即总是从相同的URI中检索相同的资源，但它依赖于处理请求的代码来解析查询字符串并返回适当的HTTP响应。同样，这个方案也会使得 HATEOAS 的实现复杂化，因为所有链接都需要包含版本号。&lt;/p&gt;

&lt;h2 id=&quot;头部版本&quot;&gt;头部版本&lt;/h2&gt;
&lt;p&gt;头部版本指客户端将版本号添加在自定义头中，随请求一起发送给服务端，例如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET https://exmaple.xueyufish.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;相比较前两种，此种方式实现 HATEOAS 需要在链接中包含自定义头。&lt;/p&gt;

&lt;h2 id=&quot;媒体类型版本&quot;&gt;媒体类型版本&lt;/h2&gt;
&lt;p&gt;媒体类型版本值客户端发起请求时将版本号放置在 Accept 头中。客户端通过 Accept 头来指定期望从服务端接收的媒体类型的列表，例如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET https://exmaple.xueyufish.com/customers/3 HTTP/1.1
Accept: application/vnd.example-xueyufish.v1+json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上述请求的 Accept 头中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vnd.example-xueyufish.v1&lt;/code&gt; 告诉服务端返回版本 1 的资源，同时 json 元素通知服务端响应体以 json 格式返回。&lt;/p&gt;

&lt;p&gt;web 服务端接收请求后，以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type&lt;/code&gt; 头返回:&lt;/p&gt;
&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;OK&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/exmaple.xueyufish.com.v1+json; charset=utf-8&lt;/span&gt;

{&quot;id&quot;:3,&quot;name&quot;:&quot;Contoso LLC&quot;,&quot;address&quot;:&quot;1 Microsoft Way Redmond WA 98053&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果 Accept 头为指定任何媒体类型或者不匹配，服务端将返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;406(Not Acceptable)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;相比较而言，此种方法应该是 RESTful APIs 版本实现中最好的一种，对 HATEOAS 支持也最好。&lt;/p&gt;

&lt;h1 id=&quot;安全性&quot;&gt;安全性&lt;/h1&gt;
&lt;h2 id=&quot;ssl&quot;&gt;SSL&lt;/h2&gt;
&lt;p&gt;无论在什么情况下，都启用 SSL，在今天的互联网上是最保险的。始终使用 SSL 的另一个优点是因为可以使用简单的访问令牌而无需对每个 API 请求进行签名校验，从而简化了身份验证工作。&lt;/p&gt;

&lt;p&gt;要注意的是对 API URL 的非 SSL 访问，不可以重定向到它们的 SSL 对应端，而应当抛出一个错误。&lt;/p&gt;

&lt;h2 id=&quot;认证&quot;&gt;认证&lt;/h2&gt;
&lt;p&gt;RESTful API 应该是无状态的，意味着请求身份验证不应依赖于 cookie 或者 session 来实现。相反，每个请求都应该带有某种类型的身份验证凭据。&lt;/p&gt;

&lt;p&gt;通过始终使用 SSL，可以将身份验证凭据简化为随机生成的访问令牌，该令牌在 &lt;a href=&quot;https://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;HTTP Basic Auth&lt;/a&gt; 的用户名字段中提供。它可以通过浏览器进行交互，如果从服务器接收到 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401 Unauthorized&lt;/code&gt;，浏览器可以弹出一个询问凭据的提示。&lt;/p&gt;

&lt;p&gt;但是，上述这种方式只有在需要将 access_token 从管理接口复制到 API 消费者环境的情况下才适用。在不可能这样做的情况下，例如对于第三方应用，应该使用 &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth2&lt;/a&gt; 向第三方提供安全令牌传输。OAuth2 使用 &lt;a href=&quot;https://tools.ietf.org/html/rfc6750&quot;&gt;Bearer Token&lt;/a&gt;，它的底层传输加密还依赖于 SSL。&lt;/p&gt;

&lt;p&gt;对于需要支持 JSONP 请求的 API，因为 JSONP 请求无法发送 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP Basic Auth&lt;/code&gt; 凭证或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bearer token&lt;/code&gt;，在这种情况下，可以在查询字符串中添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; 参数。&lt;/p&gt;

&lt;h1 id=&quot;缓存&quot;&gt;缓存&lt;/h1&gt;
&lt;p&gt;HTTP 协议本身提供了内置的缓存框架，通常我们只需要包含一些额外的出站响应头，并在收到一些入站请求头时进行一些验证。常见的方式包括 &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_ETag&quot;&gt;ETag&lt;/a&gt; 和 &lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29&quot;&gt;Last-Modified&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;etag&quot;&gt;Etag&lt;/h2&gt;
&lt;p&gt;当生成响应时，在 HTTP Etag 头中包含一个 hash 或者响应内容的校验和，如果响应内容发生改变，那么 Etag 值也将发生改变。如果入站请求包含一个携带匹配 Etag 值的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If-None-Match&lt;/code&gt; 头，API 将返回 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;304 Not Modified&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;last-modified&quot;&gt;Last-Modified&lt;/h2&gt;
&lt;p&gt;与 Etag 类似，不同在于使用 timestamps 来表示。Last-Modified 响应头包含一个 &lt;a href=&quot;http://www.ietf.org/rfc/rfc1123.txt&quot;&gt;RFC 1123&lt;/a&gt; 格式的时间戳，在内容修改后根据 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If-Modified-Since&lt;/code&gt; 进行校验&lt;/p&gt;

&lt;h1 id=&quot;频率限制&quot;&gt;频率限制&lt;/h1&gt;
&lt;p&gt;为了防止 API 被滥用或攻击，标准做法是添加某种速率限制, &lt;a href=&quot;http://tools.ietf.org/html/rfc6585&quot;&gt;RFC6585&lt;/a&gt; 引入了 HTTP 状态码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;429(Too Many Requests)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;目前比较常用的方式是服务端在 API 响应中加入以下自定义头：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;X-Rate-Limit-Limit: 表示时间窗内允许的最大请求数&lt;/li&gt;
  &lt;li&gt;X-Rate-Limit-Remaining: 表示当前时间窗内的剩余请求数&lt;/li&gt;
  &lt;li&gt;X-Rate-Limit-Reset: 表示当前时间窗剩余的秒数&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可参见&lt;a href=&quot;https://developer.github.com/v3/#rate-limiting&quot;&gt;Github Rate Limiting&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;错误处理&quot;&gt;错误处理&lt;/h1&gt;
&lt;p&gt;API 应始终返回合理的 HTTP 状态代码。API 错误通常分为两种类型：客户端错误返回 4xx 系列状态码, 服务端错误范围 5xx 系列状态码。对于 API ，应该将所有 4xx 错误都带有可标准化的 JSON 错误表示。如果可能的化（即如果负载均衡器和反向代理可以创建自定义的错误体），应该对 5xx 系列状态码也进行标准化错误表示。&lt;/p&gt;

&lt;p&gt;当错误发生时，JSON 类型的错误体应当为开发者提供以下错误信息：一条有用的错误消息、一个唯一的错误代码以及尽可能的详细描述。例如：&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Something bad happened :(&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;More details about the error here&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PUT、PATCH 和 POST 请求的验证错误需要字段细分，对于验证失败，最好使用固定的顶级错误代码进行，并在其他错误字段中提供详细的错误，例如：&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Validation Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;errors&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5432&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;first_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;First name cannot have fancy characters&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
       &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5622&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
       &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
       &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Password cannot be blank&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;http-状态码&quot;&gt;HTTP 状态码&lt;/h1&gt;
&lt;p&gt;HTTP 协议定义了一系列有意义的状态代码(&lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_HTTP_status_codes&quot;&gt;List of HTTP status codes&lt;/a&gt;)，这些代码可以从 API 返回，用来帮助 API 使用者路由或者处理他们的响应。结合上文所述，其中主要常用的状态码总结如下：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 OK&lt;/code&gt;: 成功的 GET, PUT, PATCH 或者 DELETE 请求响应，也可用于非资源创建场景下的 POST 请求。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;201 Created&lt;/code&gt;: 响应创建新资源的 POST 请求，应同时包含 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Location&lt;/code&gt; 头指向一个新创建的资源 URI。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;204 No Content&lt;/code&gt;: 对不会返回正文的成功请求的响应。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;304 Not Modified&lt;/code&gt;: 当 HTTP 缓存头被使用时返回。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400 Bad Request&lt;/code&gt;: 请求的格式不正确，例如请求的 body 部分无法解析&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401 Unauthorized&lt;/code&gt;: 表示未提供身份验证详细信息或提供的身份验证详细信息无效。如果从浏览器使用 API，可以触发弹出请求认证窗口&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;403 Forbidden&lt;/code&gt;: 表示身份验证成功但经过身份验证的用户无权访问资源&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;404 Not Found&lt;/code&gt;: 表示请求一个不存在的资源&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;405 Method Not Allowed&lt;/code&gt;: 表示请求的方法对当前请求的资源不支持。例如对一个创建资源的 POST 方法发起 GET 请求。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;410 Gone&lt;/code&gt;: 用于表示请求资源不可用并且在未来版本中也不会再支持，常被用于旧 API 版本的总体响应。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;415 Unsupported Media Type&lt;/code&gt;: 请求实体具有服务器或资源不支持的媒体类型&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;422 Unprocessable Entity&lt;/code&gt;: 请求的格式正确，但由于语义错误而无法执行，主要用于验证错误&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;429 Too Many Requests&lt;/code&gt;: 请求由于流量限制被拒绝&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design&quot;&gt;API design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://geemus.gitbooks.io/http-api-design/content/en/&quot;&gt;Heroku Platform API Guidelines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api&quot;&gt;Best Practices for Designing a Pragmatic RESTful API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;tttps://developer.github.com/v3/&quot;&gt;Github API v3&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 30 Jan 2019 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2019/01/30/api-design-guidelines/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2019/01/30/api-design-guidelines/</guid>
        
        <category>架构设计</category>
        
        <category>REST</category>
        
        
      </item>
    
      <item>
        <title>聊聊缓存更新策略</title>
        <description>&lt;p&gt;在分布式场景下，数据库往往是对资源消耗最严重，也是最容易出问题的地方。由于绝大多数情况下的应用还是读远远大于写，对 select 操作执行的频率会非常高；另外，伴随着 select 查询往往会有其他复杂的 join、group、order、like 等语义，对数据库性能消耗会非常大。因此，在实时性要求不是非常高的情况下，我们往往需要缓存对数据库保护，也可以提升查询速度。&lt;/p&gt;

&lt;p&gt;此外，在现在的互联网环境下，移动互联网前端应用对后端的访问频率非常高，各种 API 调用非常频繁，理论上 API 层面也需要缓存进行保护，在后端服务短时间因为网络抖动、数据提供不可达等情况发生时，可以呈现相应的结果页面给用户，不至于影响用户体验。从业务场景分析，大多数应用对实时性要求也并不是特别高，所以也可以应用缓存。&lt;/p&gt;

&lt;p&gt;这里我们主要讨论缓存和后端数据之间的同步问题，对于缓存的雪崩、穿透等其他问题以及缓存的实现，后面再做分析。&lt;/p&gt;

&lt;p&gt;目前业内主要流行的有以下三种模式：&lt;/p&gt;

&lt;h1 id=&quot;cache-aside-模式&quot;&gt;Cache-Aside 模式&lt;/h1&gt;
&lt;p&gt;Cache-Aside 是业内最为常用的模式，也就是在业务代码内管理和维护缓存。&lt;/p&gt;

&lt;p&gt;在读场景下，先从缓存中获取数据，如果没有命中，则从数据库读取并将数据放入缓存以供下次使用；&lt;/p&gt;

&lt;p&gt;在写场景下，先将数据写入到存储系统，写入成功后再将数据写入到缓存；或者写入成功后将缓存数据过期，下次读取时再加载缓存。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/cache-update-strategy//cce983cbc455596091c6b802a9eb2bb4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/cache-update-strategy//212f12c3aac659f611fa5a79992d8c37.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;执行 Cache-Aside 模式的伪码如下：&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;K&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getIfPresent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readFromDatabase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;K&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;writeToDatabase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;invalidate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用 Cache-Aside 模式， 理论上会导致脏数据产生。原因是当一个客户端进行读操作，但是没有命中缓存，然后就到数据库中取数据；此时另一个客户端进行写操作，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。&lt;/p&gt;

&lt;p&gt;在实际场景中，这个情况出现的概率可能非常低，因为这个条件需要发生在读缓存时缓存失效，而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大。&lt;/p&gt;

&lt;h1 id=&quot;read-through-模式&quot;&gt;Read Through 模式&lt;/h1&gt;

&lt;p&gt;Read Through 模式在查询操作中更新缓存。当缓存没有命中时，Cache Aside 由调用方负责把数据加载入缓存，而 Read Through 则用缓存服务自己来加载，从而对应用方是透明的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/cache-update-strategy//read-through.png&quot; alt=&quot;read-through&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;write-through-模式&quot;&gt;Write Through 模式&lt;/h1&gt;

&lt;p&gt;Write Through 模式在更新数据时发生。当有数据更新的时候，如果没有命中缓存，直接更新数据库，然后返回。如果命中了缓存，则更新缓存，然后再由 Cache 自己更新数据库，其中更新缓存和更新数据库是一个同步操作。对应用方而言，操作也是是透明的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/cache-update-strategy//write-through.png&quot; alt=&quot;write-through&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;write-back-模式&quot;&gt;Write Back 模式&lt;/h1&gt;

&lt;p&gt;Write back 模式其实就是 Linux 文件系统 Page Cache 的算法。在更新数据的时候，只更新缓存，不更新数据库，而缓存会异步地批量更新数据库。算法的优势在于因为直接操作内存，所以数据的I/O操作比较快；另外因为异步，write back 还可以合并对同一个数据的多次操作，所以性能会得到提升。&lt;/p&gt;

&lt;p&gt;Write back 模式带来的问题是，数据不是强一致性的，甚至可能会丢失，并且实现逻辑比较复杂，因为需要对有哪些数据被更新做跟踪。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/cache-update-strategy//Write-back_with_write-allocation.png&quot; alt=&quot;write-back&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;参考资料&quot;&gt;参考资料&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://coolshell.cn/articles/17416.html&quot;&gt;缓存更新的套路&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://www.ehcache.org/documentation/3.5/caching-patterns.html&quot;&gt;Cache Usage Patterns&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/&quot;&gt;Caching Strategies and How to Choose the Right One&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 20 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/07/20/cache-update-strategy/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/07/20/cache-update-strategy/</guid>
        
        <category>缓存</category>
        
        <category>分布式</category>
        
        
      </item>
    
      <item>
        <title>分布式锁及其实现方式简介</title>
        <description>&lt;p&gt;通常，对于多线程访问共享资源的应用，我们往往需要对共享资源进行加锁保护，防止数据发生错乱；而在分布式场景下，分布式锁对于共享资源的正确性、有效性起着非常重要的作用。使用分布式锁，同样可以防止数据错乱行为的发生；此外，也可以避免不必要的外部请求，从而提升系统效率。&lt;/p&gt;

&lt;p&gt;多线程场景下锁的实现各个语言或系统提供了不同的方式，而对于分布式锁，目前比较流行的主要是通过 DB、Redis、Zookeeper 三种方式实现。 Redis官方关于分布式锁的文档中总结了分布式锁需要的三个特点：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt;：在任意时刻，只有一个客户端可以获取锁；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;无死锁&lt;/strong&gt;：客户端最终一定可以获得锁，即使锁住某个资源的客户端在释放锁之前崩溃或网络不可达；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;容错性&lt;/strong&gt;： 只要锁服务集群中的大部分节点存活，客户端就可以进行加锁解锁操作；&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;基于数据库实现&quot;&gt;基于数据库实现&lt;/h1&gt;
&lt;p&gt;基于数据库的实现方式很多，主要思想是在数据表上加排他锁。当请求来到时对表记录进行锁检查，如果当前没有占用锁，则可以对共享资源进行操作；等到操作完成后释放锁。&lt;/p&gt;

&lt;p&gt;以 MySQL 数据库为例，创建数据表：&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`dlock`&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`lockId`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;UNSIGNED&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;主键&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`txId`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;UNSIGNED&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;锁事务ID&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`lockName`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;锁名称&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`createdAt`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;创建时间&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`updatedAt`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COMMENT&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;修改时间&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`lockId`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`idx_txid`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`txId`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;具体实现方式：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;当请求到来时，先通过 select 语句判断当前事务 ID 是否被占用，即在表中是否已经存在 &lt;code&gt;txId=currentTxId&lt;/code&gt; 的记录；&lt;/li&gt;
  &lt;li&gt;如果存在，则表示当前事务已经被锁定，没办法获取锁；如果不存在，则在数据表中 insert 一条 &lt;code&gt;txId=currentTxId&lt;/code&gt; 的记录；&lt;/li&gt;
  &lt;li&gt;当请求执行完成后，如果需要释放锁，执行 delete 语句删除表中记录。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;基于数据库的分布式实现相对而言最简单，不过存在着不少问题，主要包括：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对数据库依赖较高，如果需要实现高可用相对比较复杂；&lt;/li&gt;
  &lt;li&gt;没有自动失效功能，一旦解锁操作失败，会导致锁记录一直在数据库中，其他线程无法再获得到锁；&lt;/li&gt;
  &lt;li&gt;insert 操作一旦失败会直接报错，没有获得锁的线程并不会进入排队队列，要想再次获得锁就要再次触发获得锁操作；&lt;/li&gt;
  &lt;li&gt;同一个线程在没有释放锁之前无法再次获得该锁, 也就是说是非重入锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;基于数据库确实在对可用性要求不高场景下可以实现分布式锁。上述的问题也有具体的解决方案，比如：对于数据库高可用的问题，可以通过 master-salve 方式解决；对于不能自动失效的问题，可以通过业务层定时任务实现；对于非阻塞锁的问题，可以通过循环判断的方式解决；而对于非重入锁问题，也可以通过增加字段记录主机和线程号的方式解决。&lt;/p&gt;

&lt;p&gt;随着要求逐步提升，数据库方式实现的分布式锁需要业务代码进行保证，而与之相付出的成本增加甚至不一定比其他方式低。另一方面，关系型数据库对 I/O 层面的开销，如果在并发量比较大的情况下，性能也是一个需要考量的因素，特别在需要复制的场景下。&lt;/p&gt;

&lt;h1 id=&quot;基于-redis-实现&quot;&gt;基于 Redis 实现&lt;/h1&gt;
&lt;h2 id=&quot;set-nx-实现&quot;&gt;SET NX 实现&lt;/h2&gt;
&lt;p&gt;缓存相对于数据库拥有更好的性能优势，比如 Redis，在硬件条件满足的情况下，tps 可以达到 10W 左右，足以支撑分布式锁的需求。Redis 处理分布式锁的简单方式是使用 SET NX 命令:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SET resource_name my_random_value NX PX 30000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;SET NX&lt;/code&gt; 命令只会在 key 不存在的情况下给 key 赋值，PX 命令通知 Redis 保存 key 30000ms；&lt;/li&gt;
  &lt;li&gt;my_random_value 必须是全局唯一的值。这个随机数在释放锁时保证释放锁操作的安全性；&lt;/li&gt;
  &lt;li&gt;PX 操作后面的参数代表这个 key 的存活时间，即锁过期时间。如果资源被锁定超过这个时间，则锁将被自动释放；如果某个客户端获取资源后占用超过这个时间，锁也将被释放，从而引起锁争用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用 &lt;code&gt;SET NX&lt;/code&gt; 命令只有在某个 key 不存在情况才能 set 成功，这样就达到了多个进程并发去 set 同一个 key，只有一个进程能 set 成功。其他进程因为之前有其他进程把 key 设置成功了，从而会导致获取锁失败。&lt;/p&gt;

&lt;p&gt;可以通过以下Lua脚本实现为申请成功的锁解锁：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;get&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KEYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ARGV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;del&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KEYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;使用这种方式释放锁可以避免删除别的客户端获取成功的锁，因为脚本仅会删除 value 等于客户端 A 的 value 的 key。&lt;/p&gt;

&lt;h2 id=&quot;redlock-算法&quot;&gt;Redlock 算法&lt;/h2&gt;
&lt;p&gt;Redis 作者鉴于 Redis 单点数据丢失的问题，提出了 Redlock 算法，旨在保证分布式锁的高可用。这个算法也引起了一定程度上的争议。&lt;/p&gt;

&lt;p&gt;Redlock 算法假设有 N 个 Redis 节点，这些节点互相独立，一般设置为 N=5，这 N 个节点运行在不同的机器上以保持物理层面的独立。算法流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;客户端获取当前 Unix 时间，以毫秒为单位;&lt;/li&gt;
  &lt;li&gt;客户端依照上文所述方式尝试获取 N 个节点的锁，这 N 个节点以相同的 key 和 value 获取锁。客户端需要设置接口访问超时时间，这个接口超时时间需远远小于锁超时时间，比如锁自动释放的时间是10s，那么接口超时大概设置5-50ms。这样可以在有 redis 节点宕机后，访问该节点时能尽快超时，而减小锁的正常使用。&lt;/li&gt;
  &lt;li&gt;客户端使用当前时间减去开始获取锁时间（步骤1记录的时间）就得到获取锁使用的时间。当且仅当从大多数（这里是3个节点）的Redis节点都取到锁，并且使用的时间小于锁失效时间时，锁才算获取成功。&lt;/li&gt;
  &lt;li&gt;如果取到了锁，key 的真正有效时间等于有效时间减去获取锁所使用的时间（步骤3计算的结果）。&lt;/li&gt;
  &lt;li&gt;如果因为某些原因，获取锁失败（没有在至少 N/2+1 个 Redis 实例取到锁或者取锁时间已经超过了有效时间），客户端应该在所有的Redis实例上进行解锁（即便某些Redis实例根本就没有加锁成功）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用 Redlock 算法，可以保证在 N/2-1 个客户端实例不可用时，整个分布式锁依然可用。&lt;/p&gt;

&lt;p&gt;关于 Redlock 算法，有个作者在其一篇文章&lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;How to do distributed locking&lt;/a&gt;中提出了质疑，作者认为如果发生Full GC或者其他情况的业务暂停导致某个持有锁的应用锁释放，有可能引起数据不一致：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果 Client1 先获取到了锁；&lt;/li&gt;
  &lt;li&gt;其他 Client 在等待 Client1 的工作完成；&lt;/li&gt;
  &lt;li&gt;这时候如果 Client1 被挂在了某些事情上，获取 CPU 被别的进程占满，或是不巧碰上了 FullGC，导致 Client1 花了超过平时几倍的时间；&lt;/li&gt;
  &lt;li&gt;锁服务在锁超时时间到了以后自动释放锁；&lt;/li&gt;
  &lt;li&gt;Client2 获取锁并且更新资源；&lt;/li&gt;
  &lt;li&gt;过阵 Client1 处理完密集事务以后再去更新资源，把 Client2 更新的冲掉了。悲剧发生了，数据出错了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/distributed-lock/unsafe-lock.png&quot; alt=&quot;unsafe-lock&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为了解决这个问题，文章作者引入了栅栏(fencing)技术，在每个写请求中附加一个自增的版本号，这就是乐观锁的实现：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;锁服务需要有一个单调递增的版本号；&lt;/li&gt;
  &lt;li&gt;写数据时，也要带上自己的版本号；&lt;/li&gt;
  &lt;li&gt;数据库服务需要保存数据的版本号，然后对请求做检查。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/distributed-lock/fencing-tokens.png&quot; alt=&quot;fencing-tokens&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Redis 作者后来对这篇文章中提出的问题进行了相应的评论，具体参见&lt;a href=&quot;http://antirez.com/news/101&quot;&gt;Is Redlock safe&lt;/a&gt;这篇文章。不过个人认为，基于 redis 实现的分布式锁，如果需要强一致性保证，还是需要增加版本号做保证，上述文章中提到的问题也切实有可能发生，并且 HBase 中就确实发生过&lt;a href=&quot;https://www.slideshare.net/enissoz/hbase-and-hdfs-understanding-filesystem-usage&quot;&gt;Hbase and HDFS Understanding filesystem usage in HBase&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;当然，作者提出的如果增加版本号也需要有一个分布式服务来生成单调自增版本号的问题是存在的，也会使得系统的复杂性提升；因为 Redlock 的实现机制实际上是在尽可能的做到分布式一致性，相比较而言，个人觉得基于 Paxos 或者 Raft 等一致性协议实现更适合。&lt;/p&gt;

&lt;h1 id=&quot;基于-zookeeper-实现&quot;&gt;基于 Zookeeper 实现&lt;/h1&gt;

&lt;p&gt;Zookeeper 作为分布式协调服务在业内已经被广泛使用，它基于 zab 协议实现, 对 Zookeeper 写入请求会转发到 leader，leader 写入完成，并同步到其他节点，直到所有节点都写入完成，才返回客户端写入成功。&lt;/p&gt;

&lt;p&gt;Zookeeper 实现分布式锁大的致流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;首先创建一个持久化的根节点，假设为 &lt;code&gt;/lock&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;客户端连接 Zookeeper，并在根节点 &lt;code&gt;/lock&lt;/code&gt; 下创建临时有序的子节点，第一个客户端对应的子节点为 &lt;code&gt;/lock/lock-000&lt;/code&gt;，第二个为 &lt;code&gt;/lock/lock-001&lt;/code&gt;，以此类推。&lt;/li&gt;
  &lt;li&gt;客户端获取 &lt;code&gt;/lock&lt;/code&gt; 下的子节点列表，判断自己创建的子节点是否为当前子节点列表中序号最小的子节点，如果是则认为获得锁，否则监听自己创建的节点之前一位子节点的删除消息，获得子节点变更通知后重复此步骤直至获得锁；&lt;/li&gt;
  &lt;li&gt;执行业务代码；&lt;/li&gt;
  &lt;li&gt;完成业务流程后，删除对应的子节点释放锁。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以看出，Zookeeper 具备几个特质，让它非常适合作为分布式锁服务：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;支持 watcher 机制，这样实现阻塞锁。通过 watch 锁数据，等到数据被删除，Zookeeper 会通知客户端去重新竞争锁;&lt;/li&gt;
  &lt;li&gt;支持临时节点，即客户端写入的数据是临时数据，在客户端宕机后，临时数据会被删除，这样就实现了锁的异常释放;&lt;/li&gt;
  &lt;li&gt;节点支持保存数据信息，这样客户端主机名和线程号可以被方便记录，下次想要获取锁的时候和当前最小的节点中的数据比对如果和自己的信息一样，那么自己直接获取到锁，如果不一样就再创建一个临时的顺序节点，参与排队；&lt;/li&gt;
  &lt;li&gt;Zookeeper 天然支持集群部署的特性，只要集群中有半数以上的机器存活，就可以对外提供服务，所以对高可用提供很好的保证；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用 Zookeeper 也有可能带来并发问题，只是并不常见而已。比如，由于网络抖动等原因客户端 zk 集群的 session 连接断开，zk 以为客户端不可用，就会删除临时节点，这时候其他客户端可以获取到分布式锁，就可能产生并发问题。这个问题不常见是因为 zk 有重试机制，一旦 zk 集群检测不到客户端的心跳，就会重试，Curator 客户端支持多种重试策略，多次重试之后还不行的话才会删除临时节点。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://curator.apache.org/&quot;&gt;curator 客户端&lt;/a&gt;对 zookeeper 提供了一些包装，可以参考其对分布式锁相关实现的源码。&lt;/p&gt;

&lt;p&gt;综合来看，Zookeeper 方式的分布式锁实现，相对于其他两种方式在可用性、正确性等方面更为可靠，复杂度也不高，相比于 redis 的实现方式，唯一不足可能就是在性能方面稍微有所欠缺，不过应该也在可以接受的范围之内。&lt;/p&gt;

&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://redis.io/topics/distlock&quot;&gt;Distributed Locks With Redis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;How to do distributed locking&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.alibabacloud.com/forum/read-453&quot;&gt;Implementation of distributed locks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://antirez.com/news/101&quot;&gt;Is Redlock safe&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zookeeper.apache.org/doc/r3.4.4/recipes.html&quot;&gt;ZooKeeper Recipes and Solutions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://curator.apache.org/&quot;&gt;curator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.hollischuang.com/archives/1716&quot;&gt;分布式锁的几种实现方式&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/&quot;&gt;聊一聊分布式锁的设计&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Wed, 18 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/07/18/distributed-lock/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/07/18/distributed-lock/</guid>
        
        <category>分布式</category>
        
        <category>Redis</category>
        
        <category>Mysql</category>
        
        <category>Zookeeper</category>
        
        
      </item>
    
      <item>
        <title>Kong Api网关简介(二) 插件</title>
        <description>&lt;p&gt;Kong 通过强大的插件机制进行了类似AOP切面的方式操作，提供了日志、认证、限流等操作。官方给出了许多成熟插件可供选择，包括：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;认证：&lt;a href=&quot;https://docs.konghq.com/plugins/basic-authentication/&quot;&gt;Basic Authentication&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/key-authentication&quot;&gt;Key Authentication&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/oauth2-authentication&quot;&gt;OAuth 2.0 Authentication&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/jwt&quot;&gt;JWT&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/ldap-authentication&quot;&gt;LDAP Authentication&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;安全：&lt;a href=&quot;https://docs.konghq.com/plugins/acl&quot;&gt;ACL&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/cors/&quot;&gt;CORS&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/dynamic-ssl/&quot;&gt;Dynamic SSL&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/ip-restriction/&quot;&gt;IP Restriction&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/bot-detection/&quot;&gt;Bot Detection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;传输控制：&lt;a href=&quot;https://docs.konghq.com/plugins/request-size-limiting/&quot;&gt;Request Size Limiting&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/rate-limiting/&quot;&gt;Rate Limiting&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/response-rate-limiting/&quot;&gt;Response Rate Limiting&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/request-termination/&quot;&gt;Request Termination&lt;/a&gt;、&lt;/li&gt;
  &lt;li&gt;分析监控：&lt;a href=&quot;https://docs.konghq.com/plugins/prometheus/&quot;&gt;Prometheus&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/zipkin/&quot;&gt;Zipkin&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/datadog/?_ga=2.98021463.993409543.1531102804-1191622050.1530857411&quot;&gt;Datadog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;日志：&lt;a href=&quot;https://docs.konghq.com/plugins/tcp-log/&quot;&gt;TCP Log&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/udp-log/&quot;&gt;UDP Log&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/http-log/&quot;&gt;HTTP Log&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/file-log/&quot;&gt;File Log&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/statsd/&quot;&gt;StatsD&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/syslog/&quot;&gt;Syslog&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/loggly/&quot;&gt;Loggly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;请求响应调整：&lt;a href=&quot;https://docs.konghq.com/plugins/request-transformer/&quot;&gt;Request Transformer&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/response-transformer/&quot;&gt;Response Transformer&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/correlation-id/&quot;&gt;Correlation ID&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Serverless：&lt;a href=&quot;https://docs.konghq.com/plugins/serverless-functions/&quot;&gt;Serverless Functions&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/azure-functions/&quot;&gt;Azure Functions&lt;/a&gt;、&lt;a href=&quot;https://docs.konghq.com/plugins/aws-lambda/&quot;&gt;AWS Lambda&lt;/a&gt;、
&lt;a href=&quot;https://docs.konghq.com/plugins/openwhisk/&quot;&gt;Apache OpenWhisk&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面从官网的例子 key-auth 插件来看具体使用&lt;/p&gt;

&lt;h3 id=&quot;插件使用&quot;&gt;插件使用&lt;/h3&gt;

&lt;p&gt;首先执行命令给 Service 添加一个 key-value 插件。此处假设已经存在了一个名为 baidu-service 的服务，具体服务的创建参见&lt;a href=&quot;http://www.yuxiumin.com/2018/07/08/kong-api-gateway-install/&quot;&gt;Kong Api网关简介(一) 安装运行&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;命令行执行命令：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/services/baidu-service/plugins/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;name=key-auth&apos;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Mon, 09 Jul 2018 06:53:31 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 276

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531119211000,&lt;span class=&quot;s2&quot;&gt;&quot;config&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key_in_body&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;run_on_preflight&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;hide_credentials&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;key_names&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;apikey&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;4d08e1fd-fb3f-4e5a-8a11-e20ad49c9f4b&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;enabled&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;service_id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;key-auth&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过管理API查看插件的其他管理操作，具体参见&lt;a href=&quot;https://docs.konghq.com/0.14.x/admin-api/#plugin-object&quot;&gt;Kong Admin API Plugin Object&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这时候再执行上一篇文章中执行的命令访问 8000 端口的接口地址，显示找不到 API key，说明 key-auth 插件已经起作用：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8000/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Host: baidu.com&apos;&lt;/span&gt;
HTTP/1.1 401 Unauthorized
Date: Mon, 09 Jul 2018 08:47:10 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
WWW-Authenticate: Key &lt;span class=&quot;nv&quot;&gt;realm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kong&quot;&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 41

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;message&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;No API key found in request&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为了继续测试，我们创建一个consumer，执行命令：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;xueyufish@izbp13cqwumhn3wzp2j5mqz kong-gateway]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/consumers/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;username=xueyufish&quot;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Mon, 09 Jul 2018 08:52:13 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 110

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;custom_id&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531126333,&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;xueyufish&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;580046a0-bbf0-4b9f-91ad-9324976df6be&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Kong 中的消费者对象代表了一个服务的消费者或者一个用户。我们可以使用 Kong 作为消费者数据存储，或者也可以将用户列表映射到数据库，以保持 Kong 与现有主数据存储的一致性；在BFF模型下，也可以为每个业务实现，或者client定义一个消费者，例如：ios、android、pc，或者针对具体不同的业务实现具备不同的消费者，具体根据业务需要而定。&lt;/p&gt;

&lt;p&gt;然后，执行命令给创建的消费者添加一个key：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/consumers/xueyufish/key-auth/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;key=123456&apos;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Mon, 09 Jul 2018 09:05:59 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 141

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;b18b0c03-4632-4ca5-8f0c-814d04da1022&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531127160000,&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;123456&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;consumer_id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;580046a0-bbf0-4b9f-91ad-9324976df6be&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时候在访问的请求header中带上apiKey参数，可以发现能够成功访问：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Host: baidu.com&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;apikey: 123456&quot;&lt;/span&gt;
HTTP/1.1 200 OK
Content-Type: text/html&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UTF-8
Content-Length: 2381
Connection: keep-alive
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Date: Mon, 09 Jul 2018 09:09:15 GMT
Etag: &lt;span class=&quot;s2&quot;&gt;&quot;588604c8-94d&quot;&lt;/span&gt;
Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: &lt;span class=&quot;nv&quot;&gt;BDORZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;27315&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; max-age&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86400&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.baidu.com&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/
X-Kong-Upstream-Latency: 54
X-Kong-Proxy-Latency: 33
Via: kong/0.14.0

&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--STATUS&lt;/span&gt; OK--&amp;gt;&amp;lt;html&amp;gt;... 省略html内容 ...&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;插件分析&quot;&gt;插件分析&lt;/h3&gt;

&lt;h4 id=&quot;文件结构&quot;&gt;文件结构&lt;/h4&gt;

&lt;p&gt;Kong 插件的文件结构分基本插件模块和完整插件模块两种，基本插件模块结构如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;simple-plugin
├── handler.lua
└── schema.lua
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其中，handler.lua 是插件核心，它是一个接口实现，其中每个函数将在请求生命周期中的期望时刻运行。schema.lua 用于定义插件配置&lt;/p&gt;

&lt;p&gt;完整插件模块结构如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── migrations
│   ├── cassandra.lua
│   └── postgres.lua
└── schema.lua
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中，api.lua 定义管理API操作接口；daos.lua 定义插件需要并且存储在数据库的实体的DAOs列表；migrations/*.lua 定义了给定数据存储的相应迁移，通常只有当插件必须在数据库中存储自定义实体并通过daos.lua定义的DAO进行交互时，迁移才是必要的。&lt;/p&gt;

&lt;p&gt;具体关于文件结构的描述参见&lt;a href=&quot;https://docs.konghq.com/0.14.x/plugin-development/file-structure/&quot;&gt;Plugin Development - File Structure&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;插件配置&quot;&gt;插件配置&lt;/h4&gt;

&lt;p&gt;Kong 插件通过schema.lua文件定义配置。schema.lua 返回一个Table类型，包含no_consumer、fields、self_check三个属性：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;属性名&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Lua 类型&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;默认值&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;no_consumer&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Boolean&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;false&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;如果为&lt;code&gt;true&lt;/code&gt;将不能应用此插件至指定消费者，只能被应用到 Services 或者 Routes, 例如：认证插件&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;fileds&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Table&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;{}&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;插件的 schema，使用一个键值对定义可用属性和他们的规则&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;self_check&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Function&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;nil&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;如果在接受插件配置之前需要进行自定义验证，需要实现此函数&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;schema.lua 文件样本如下：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;no_consumer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- this plugin will only be applied to Services or Routes,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;-- Describe your plugin&apos;s configuration&apos;s schema here.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;self_check&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plugin_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dao&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_updating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;-- perform any custom verification&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;更多具体配置参见 &lt;a href=&quot;https://docs.konghq.com/0.14.x/plugin-development/plugin-configuration/&quot;&gt;Plugin Development - Store Configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;从 key-auth 插件的 schema 文件可以看出，定义了五个 field, 其中刚用到的 key_name 默认为必填，类型为 array, 默认值为一个名为default_key_names的function，同时附加了 check_keys function 用于验证header。&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;check_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ipairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;validate_header_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&apos;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&apos; is illegal: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;


&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;default_key_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_names&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;apikey&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;no_consumer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key_names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;array&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default_key_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hide_credentials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;boolean&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;逻辑实现&quot;&gt;逻辑实现&lt;/h4&gt;

&lt;p&gt;Kong 插件可以在请求/响应生命周期中的几个入口点注入自定义逻辑，插件实现者必须在 handler.lua 中实现 base_plugin.lua 文件中的一个或多个接口。
base_plugin.lua 文件中的几个方法如下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;函数名&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;LUA-NGINX-MODULE Context&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:init_worker()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#init_worker_by_lua&quot;&gt;init_worker_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;在每个 Nginx 工作进程启动时执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:certificate()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block&quot;&gt;ssl_certificate_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;在SSL握手阶段的SSL证书服务阶段执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:rewrite()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#rewrite_by_lua&quot;&gt;rewrite_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;从客户端接收作为重写阶段处理程序的每个请求执行。在这个阶段，无论是API还是消费者都没有被识别，因此这个处理器只在插件被配置为全局插件时执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:access()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#access_by_lua&quot;&gt;access_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;为客户的每一个请求而执行，并在它被代理到上游服务之前执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:header_filter()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#header_filter_by_lua&quot;&gt;header_filter_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;从上游服务接收到所有响应头字节时执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:body_filter()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#body_filter_by_lua&quot;&gt;body_filter_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;从上游服务接收的响应体的每个块时执行。由于响应流回客户端，它可以超过缓冲区大小，因此，如果响应较大，该方法可以被多次调用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;:log()&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://github.com/openresty/lua-nginx-module#log_by_lua&quot;&gt;log_by_lua&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;当最后一个响应字节已经发送到客户端时执行&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;key-auth 插件的 handler.lua 文件实现了其中的 access() 方法：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;KeyAuthHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;KeyAuthHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;-- check if preflight request and whether it should be authenticated&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_on_preflight&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;OPTIONS&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ngx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authenticated_credential&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anonymous&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;-- we&apos;re already authenticated, and we&apos;re configured for using anonymous,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;-- hence we&apos;re in a logical OR between auth methods and we&apos;re already done.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_authentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anonymous&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;-- get anonymous user&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;consumer_cache_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dao&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;consumers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anonymous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;consumer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;singletons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;consumer_cache_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                                 &lt;span class=&quot;n&quot;&gt;load_consumer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                                 &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anonymous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;responses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send_HTTP_INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;set_consumer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;consumer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;responses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除此之外，Kong 还提供了数据访问、插件缓存、管理API等可以自定义功能，具体参见&lt;a href=&quot;https://docs.konghq.com/0.14.x/plugin-development/&quot;&gt;Kong Plugin Development&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;自定义插件&quot;&gt;自定义插件&lt;/h3&gt;

&lt;p&gt;下面创建一个 hello-world 自定义插件，插件本身并没有太大意义，只为了演示流程。&lt;/p&gt;

&lt;h4 id=&quot;准备&quot;&gt;准备&lt;/h4&gt;

&lt;p&gt;为了更方便测试，先卸载之前镜像，删除本地的postgres数据库重新pull。&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose down
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; postgresql/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 kong-gateway/plugins目录下添加两个文件 handler.lua 和 schema.lua，内容如下:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- handler.lua&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasePlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;kong.plugins.base_plugin&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloWorldHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BasePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ngx_log&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ngx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ngx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;HelloWorldHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PRIORITY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HelloWorldHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;HelloWorldHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hello-world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HelloWorldHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;HelloWorldHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;say_hello&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ngx_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;============ Hello World! ============&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ngx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello-World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;=== Hello World ===&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ngx_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;============ Bye World! ============&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ngx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello-World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;=== Bye World ===&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloWorldHandler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- schema.lua&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;no_consumer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;self_check&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plugin_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dao&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_updating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同时，在 kong-gateway/kong.conf 配置文件中增加插件配置:&lt;/p&gt;
&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时目录结构如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kong-gateway
| -- config
	| -- kong.conf
| -- plugins
  | -- hello-world
    | -- handler.lua
    | -- schema.lua
| -- postgresql
| -- docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于在 docker-compose.yml 中指定了 plugins 目录的挂载点为 /etc/kong/plugins，此时可以查看刚添加的 hello-world 目录已经被挂载到 docker 容器中：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;kong &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-la&lt;/span&gt; /etc/kong/plugins/hello-world
total 16
drwxrwxr-x    2 1000     1000          4096 Jul 10 02:38 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
drwxrwxr-x    3 1000     1000          4096 Jul 10 02:38 ..
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;    1 1000     1000           822 Jul 10 02:04 handler.lua
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;    1 1000     1000           345 Jul  6 09:25 schema.lua
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;重新迁移数据库，启动 kong:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose run kong kong migrations up
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up &lt;span class=&quot;nt&quot;&gt;--no-recreate&lt;/span&gt; –d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;容器启动以后，根据&lt;a href=&quot;http://www.yuxiumin.com/2018/07/08/kong-api-gateway-install/&quot;&gt;Kong Api网关简介(一) 安装运行&lt;/a&gt;中的内容添加Service、Route，再添加插件：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/plugins/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;name=hello-world&apos;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Tue, 10 Jul 2018 06:51:12 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 137

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531205473000,&lt;span class=&quot;s2&quot;&gt;&quot;config&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;say_hello&quot;&lt;/span&gt;:true&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;bf733b5a-c49f-46b3-a680-a7e3a75414ac&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;enabled&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;hello-world&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;运行&quot;&gt;运行&lt;/h4&gt;

&lt;p&gt;上述完成以后，终端执行curl进行测试:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://localhost:8000/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Host: baidu.com&apos;&lt;/span&gt;
HTTP/1.1 200 OK
Content-Type: text/html&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UTF-8
Content-Length: 2381
Connection: keep-alive
Hello-World: &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; Hello World &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt;
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Date: Tue, 10 Jul 2018 07:01:31 GMT
Etag: &lt;span class=&quot;s2&quot;&gt;&quot;588604c8-94d&quot;&lt;/span&gt;
Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: &lt;span class=&quot;nv&quot;&gt;BDORZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;27315&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; max-age&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86400&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.baidu.com&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/
X-Kong-Upstream-Latency: 62
X-Kong-Proxy-Latency: 9
Via: kong/0.14.0

&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--STATUS&lt;/span&gt; OK--&amp;gt;&amp;lt;html&amp;gt;省略 html&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看见 header 中增加了 &lt;code&gt;Hello-world&lt;/code&gt; 内容，说明插件生效了。&lt;/p&gt;
</description>
        <pubDate>Mon, 09 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/07/09/kong-api-gateway-plugin/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/07/09/kong-api-gateway-plugin/</guid>
        
        <category>开源项目</category>
        
        <category>Kong</category>
        
        <category>Nginx</category>
        
        <category>Lua</category>
        
        
      </item>
    
      <item>
        <title>Kong Api网关简介(一) 安装运行</title>
        <description>&lt;p&gt;Kong 是 Mashape 开源的高性能高可用 API 网关和 API 管理服务层。它基于 OpenResty 进行 API 管理，并提供了插件实现 API 的 AOP。最近因工作的需要研究了 Kong 的相关应用实现。&lt;/p&gt;

&lt;h3 id=&quot;准备&quot;&gt;准备&lt;/h3&gt;

&lt;p&gt;kong 的官方文档提供了基于不同平台的安装方式，为了方便，这里使用 docker-compose 进行。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;kong-gateway &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;kong-gateway
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;touch &lt;/span&gt;docker-compose.yml
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;config plugins postgresql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述创建名为 kong-gateway 的目录并且进入，然后在 kong-gateway 目录中创建 docker-compose.yml 文件，并且创建了 config、 plugins、 postgresql 三个文件夹。其中, config 文件夹用于存放 kong 网关配置, plugins 文件夹用于存放自定义插件, postgresql 文件夹用于存放 postgresql 数据文件，这三个文件夹将被挂载到 docker 容器中。 docker-compose.yml 文件定义如下：&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3&apos;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:9.6&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5432:5432&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;POSTGRES_USER=kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;POSTGRES_DB=kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;POSTGRES_PASSWORD=kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PGDATA=/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kong-database&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./postgresql/data:/var/lib/postgresql/data&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;kong&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kong:latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kong&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_DATABASE=postgres&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_PG_HOST=kong-database&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_PG_USER=kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_PG_PASSWORD=kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_CASSANDRA_CONTACT_POINTS=kong-database&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_PROXY_ACCESS_LOG=/dev/stdout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_ADMIN_ACCESS_LOG=/dev/stdout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_PROXY_ERROR_LOG=/dev/stderr&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KONG_ADMIN_ERROR_LOG=/dev/stderr&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8000:8000&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8001:8001&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8443:8443&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;7946:7946&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;7946:7946/udp&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./config:/etc/kong&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./plugins:/etc/kong/plugins&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;depends_on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;config 文件夹下放置 kong 的配置文件 kong.conf，简单定义如下：&lt;/p&gt;
&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; = /&lt;span class=&quot;n&quot;&gt;etc&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;kong&lt;/span&gt;/

&lt;span class=&quot;n&quot;&gt;log_level&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;debug&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;proxy_listen&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;8000&lt;/span&gt;, &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;8443&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ssl&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;admin_listen&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;8001&lt;/span&gt;, &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;8444&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ssl&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;database&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;postgres&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_host&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;127&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_port&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;5432&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_user&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;kong&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_password&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;kong&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_database&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;kong&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_ssl&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;off&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pg_ssl_verify&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述准备好后的 kong-gateway文件夹目录如下:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kong-gateway
| -- config
	| -- kong.conf
| -- plugins
| -- postgresql
| -- docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;启动&quot;&gt;启动&lt;/h3&gt;

&lt;p&gt;首先准备数据库，执行下面命令：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose run kong kong migrations up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行过程中将拉取 docker-compose.yml 中定义的 postgres:9.6 镜像和 kong:latest镜像，并执行数据库迁移操作。&lt;/p&gt;

&lt;p&gt;迁移完成后，执行下面命令，启动 kong 网关:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up &lt;span class=&quot;nt&quot;&gt;--no-recreate&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
Starting kong-database ... &lt;span class=&quot;k&quot;&gt;done
&lt;/span&gt;Creating kong          ... &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose ps
    Name                   Command               State                                                                Ports
&lt;span class=&quot;nt&quot;&gt;-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
kong            /docker-entrypoint.sh kong ...   Up      0.0.0.0:7946-&amp;gt;7946/tcp, 0.0.0.0:7946-&amp;gt;7946/udp, 0.0.0.0:8000-&amp;gt;8000/tcp, 0.0.0.0:8001-&amp;gt;8001/tcp, 0.0.0.0:8443-&amp;gt;8443/tcp, 8444/tcp
kong-database   docker-entrypoint.sh postgres    Up      0.0.0.0:5432-&amp;gt;5432/tcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时候控制台执行 &lt;code&gt;curl http://127.0.0.1:8001/status&lt;/code&gt;可以看到管理端口输出的状态json。&lt;/p&gt;

&lt;h3 id=&quot;运行&quot;&gt;运行&lt;/h3&gt;

&lt;h4 id=&quot;servie-object&quot;&gt;Servie Object&lt;/h4&gt;

&lt;p&gt;Kong 中的 Service 对象代表了上游服务的一个抽象，主要由协议、主机名、端口、路径等组成。Service 和路由关联(一个Service可能会被关联至多个路由)。更多参见&lt;a href=&quot;https://docs.konghq.com/0.14.x/admin-api/#service-object&quot;&gt;Kong Admin Api Service Object&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;我们首先执行命令创建一个 Service, 这个 Service 名称为 baidu-service，对应的 url 地址为 &lt;code&gt;http://www.baidu.com&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/services/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;name=baidu-service&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;url=http://www.baidu.com&apos;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Mon, 09 Jul 2018 05:53:13 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 259

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;host&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;www.baidu.com&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;connect_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;protocol&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;baidu-service&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;read_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;port&quot;&lt;/span&gt;:80,&lt;span class=&quot;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;retries&quot;&lt;/span&gt;:5,&lt;span class=&quot;s2&quot;&gt;&quot;write_timeout&quot;&lt;/span&gt;:60000&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过以下命令查看所有已创建的 Service:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/services/
HTTP/1.1 200 OK
Date: Mon, 09 Jul 2018 05:55:44 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 282

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;next&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;data&quot;&lt;/span&gt;:[&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;host&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;www.baidu.com&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;connect_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;protocol&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;baidu-service&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;read_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;port&quot;&lt;/span&gt;:80,&lt;span class=&quot;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;retries&quot;&lt;/span&gt;:5,&lt;span class=&quot;s2&quot;&gt;&quot;write_timeout&quot;&lt;/span&gt;:60000&lt;span class=&quot;o&quot;&gt;}]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以根据 serviceId 查看某个具体的 Service:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/services/edbe2f0f-f11e-493b-9b59-5997e50c3de1
HTTP/1.1 200 OK
Date: Mon, 09 Jul 2018 05:57:30 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 259

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;host&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;www.baidu.com&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;connect_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;protocol&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;baidu-service&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;read_timeout&quot;&lt;/span&gt;:60000,&lt;span class=&quot;s2&quot;&gt;&quot;port&quot;&lt;/span&gt;:80,&lt;span class=&quot;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531115593,&lt;span class=&quot;s2&quot;&gt;&quot;retries&quot;&lt;/span&gt;:5,&lt;span class=&quot;s2&quot;&gt;&quot;write_timeout&quot;&lt;/span&gt;:60000&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;route-object&quot;&gt;Route Object&lt;/h4&gt;

&lt;p&gt;路由定义了匹配客户端请求的规则，每一个路由关联一个 Service，每一个 Service 有可能被多个路由关联，每一个匹配到指定的路由请求将被代理到它关联的 Service 上。
更多参见&lt;a href=&quot;https://docs.konghq.com/0.14.x/admin-api/#route-object&quot;&gt;Kong Admin Api Route Object&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;首先创建一个Route：&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://localhost:8001/services/baidu-service/routes &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;hosts[]=baidu.com&apos;&lt;/span&gt;
HTTP/1.1 201 Created
Date: Mon, 09 Jul 2018 06:24:11 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 288

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;strip_path&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;hosts&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;baidu.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;preserve_host&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;regex_priority&quot;&lt;/span&gt;:0,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;paths&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;service&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;methods&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;protocols&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;92b5b7b7-49a9-4cb5-a595-2544e86ee519&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过以下命令查看所有已创建的 Routes:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://localhost:8001/routes
HTTP/1.1 200 OK
Date: Mon, 09 Jul 2018 06:25:58 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 311

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;next&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;data&quot;&lt;/span&gt;:[&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;strip_path&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;hosts&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;baidu.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;preserve_host&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;regex_priority&quot;&lt;/span&gt;:0,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;paths&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;service&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;methods&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;protocols&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;92b5b7b7-49a9-4cb5-a595-2544e86ee519&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以根据 routeId 查看某个具体的 Route:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8001/routes/92b5b7b7-49a9-4cb5-a595-2544e86ee519
HTTP/1.1 200 OK
Date: Mon, 09 Jul 2018 06:27:32 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Access-Control-Allow-Origin: &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
Server: kong/0.14.0
Content-Length: 288

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;strip_path&quot;&lt;/span&gt;:true,&lt;span class=&quot;s2&quot;&gt;&quot;hosts&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;baidu.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;preserve_host&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;regex_priority&quot;&lt;/span&gt;:0,&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;:1531117451,&lt;span class=&quot;s2&quot;&gt;&quot;paths&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;service&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;edbe2f0f-f11e-493b-9b59-5997e50c3de1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;methods&quot;&lt;/span&gt;:null,&lt;span class=&quot;s2&quot;&gt;&quot;protocols&quot;&lt;/span&gt;:[&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;92b5b7b7-49a9-4cb5-a595-2544e86ee519&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;运行-1&quot;&gt;运行&lt;/h4&gt;

&lt;p&gt;先执行如下命令执行:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8000/
HTTP/1.1 404 Not Found
Date: Mon, 09 Jul 2018 06:33:31 GMT
Content-Type: application/json&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;utf-8
Connection: keep-alive
Server: kong/0.14.0
Content-Length: 58

&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;message&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;no route and no API found with those values&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;发现返回的是&lt;code&gt;404 Not Found&lt;/code&gt;，因为我们在 route 中定义了 host，所以需要在 header 中指定 host。 根据 Kong Admin Api 要求，添加 Route 时，methods、hosts、path三者至少选择一个。修改请求如下：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;xueyufish@izbp13cqwumhn3wzp2j5mqz kong-gateway]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--url&lt;/span&gt; http://127.0.0.1:8000/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Host: baidu.com&apos;&lt;/span&gt;
HTTP/1.1 200 OK
Content-Type: text/html&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UTF-8
Content-Length: 2381
Connection: keep-alive
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Date: Mon, 09 Jul 2018 06:38:22 GMT
Etag: &lt;span class=&quot;s2&quot;&gt;&quot;588604c8-94d&quot;&lt;/span&gt;
Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: &lt;span class=&quot;nv&quot;&gt;BDORZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;27315&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; max-age&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86400&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.baidu.com&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/
X-Kong-Upstream-Latency: 63
X-Kong-Proxy-Latency: 34
Via: kong/0.14.0

&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--STATUS&lt;/span&gt; OK--&amp;gt;&amp;lt;html&amp;gt;... 省略html内容 ...&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至此，一个简单的 kong 网关运行成功。但是正如 kong 官网所描述，它具备很多其他特性，后续将对重要的继续介绍。&lt;/p&gt;
</description>
        <pubDate>Sun, 08 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/07/08/kong-api-gateway-install/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/07/08/kong-api-gateway-install/</guid>
        
        <category>开源项目</category>
        
        <category>Kong</category>
        
        <category>Nginx</category>
        
        <category>Lua</category>
        
        
      </item>
    
      <item>
        <title>Lua学习参考</title>
        <description>&lt;p&gt;之前对 Lua 语言的强大早有耳闻，在 Nginx、Redis 等平台特别是网关和互联网秒杀等场景中发挥了非常重要的作用。可惜一直没有学习，最近因为工作的需要在研究基于 Kong 的网关实现，抽空花了几天时间进行了研究。&lt;/p&gt;

&lt;h3 id=&quot;运行&quot;&gt;运行&lt;/h3&gt;
&lt;p&gt;Lua 是类 C 的，他是大小写字符敏感的, 作为一个扩展式语言，Lua 没有 “main” 程序的概念, 它只能嵌入一个宿主程序中工作。&lt;/p&gt;

&lt;p&gt;Lua 可以像 python 一样，在命令行上运行 lua 命令后进入 shell 中执行:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;➜&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lua&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Lua&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;Copyright&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1994&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2017&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Lua&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PUC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Rio&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以把脚本存成一个文件，用命令行来运行:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;➜&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vim&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lua&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lua&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜ ~ lua hello.lua
hello world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜ ~ &lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x hello.lua
➜ ~ ./hello.lua
hello world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;语法&quot;&gt;语法&lt;/h3&gt;

&lt;h4 id=&quot;词法约定&quot;&gt;词法约定&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;标识符&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lua 的标识符为字母或者下划线开头的字母、下划线、数字序列，但是最好不要使用下划线加大写字母的标示符。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;保留字&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;elseif&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;    &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;    &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt;    &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;    &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt;    &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;repeat&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;    &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;until&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注释&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lua 的注释和 C、Java 等语言有所不同，单行注释采用如&lt;code&gt;-- comment --&lt;/code&gt;的方式，多行注释如下:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;--[[
multiline comment1
multiline comment2
--]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;基本类型&quot;&gt;基本类型&lt;/h4&gt;

&lt;p&gt;Lua 中有八种基本类型： nil, boolean, number, string, function, userdata, thread, and table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nil&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nil 类型只有一种值 nil，主要用途用于标识和别的任何值的差异, 通常, 当需要描述一个无意义的值时会用到它。一个全局变量没有被赋值以前默认值为nil, 给全局变量负nil可以删除该变量。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boolean&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Boolean 类型只有两种值：false 和 true。在控制结构的条件中除了 false 和 nil 为假，其他值都为真; Lua 认为 数字 0 和空字符串都是真。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lua 的数字只有 double 型，64位，通常不必担心 Lua 处理浮点数会慢（除非大于100,000,000,000,000），或是会有精度问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;String&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lua 的字符串是 8 位字节，所以字符串可以包含任何数值字符；Lua 中字符串是不可以修改的, 可以使用单引号或者双引号表示字符串， 还支持C类型的转义, 比如:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- 响铃       \b -- 后退     \f -- 换页     \n -- 换行     \r -- 回车      \t -- 制表&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- 纵向制表   \\ -- 反斜杠    \&quot; -- 双引号   \&apos; -- 单引号   \[ -- 左中括号  \] -- 右中括号&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;局部变量&quot;&gt;局部变量&lt;/h4&gt;
&lt;p&gt;Lua 中使用 local 创建一个局部变量。使用局部变量的好处在于可以避免命名冲突和访问局部变量的速度比全局变量更快，因尽可能使用局部变量。关于局部变量的使用和作用域，可以参考下面的代码块：&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#! /usr/local/bin/lua&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;x in while body: &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;x in then body: &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;x =&amp;gt; &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;test end, x: &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出为:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x &lt;span class=&quot;k&quot;&gt;in while &lt;/span&gt;body: 2
x &lt;span class=&quot;k&quot;&gt;in while &lt;/span&gt;body: 4
x &lt;span class=&quot;k&quot;&gt;in while &lt;/span&gt;body: 6
x &lt;span class=&quot;k&quot;&gt;in while &lt;/span&gt;body: 8
x &lt;span class=&quot;k&quot;&gt;in while &lt;/span&gt;body: 10
x &lt;span class=&quot;k&quot;&gt;in then &lt;/span&gt;body: 11
&lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;end, x: 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;控制结构&quot;&gt;控制结构&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;if语句&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- 单一 if 形式&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conditions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- if-else 形式&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conditions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- if-elseif-else 形式&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conditions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elseif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conditions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elseif&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;while语句&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;statements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;repeat-until语句&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;repeat&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;statements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;until&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conditions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;for循环&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;for 循环有两种形式：一种是数字形式，另一种是泛型形式。&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- 数字形式的 for 循环&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exp1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exp3&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是：1. 所有三个控制表达式都只被运算一次，表达式的计算在循环开始之前; 2. 控制变量 var 是局部变量自动被声明,并且只在循环内有效; 3. 循环过程中不可以改变控制变量的值，如果要退出循环，使用break语句&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- 泛型形式的 for 循环&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ipairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;多返回值&quot;&gt;多返回值&lt;/h3&gt;

&lt;p&gt;Lua 函数可以返回多个结果值，这点和 Go 语言类似。例如:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;                   &lt;span class=&quot;c1&quot;&gt;-- returns no results&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;a&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;-- returns 1 result&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;baz&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;b&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;-- returns 2 results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当调用作为表达式最后一个参数或者仅有一个参数时，根据变量个数函数尽可能多地返回多个值，不足补nil，超出舍去；其他情况下，函数调用仅返回第一个值（如果没有返回值为nil）。例如:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;-- x = &apos;a&apos;, y = &apos;b&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                   &lt;span class=&quot;c1&quot;&gt;-- x = &apos;a&apos;, &apos;b&apos; 被丢弃&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;-- x = 10, y = &apos;a&apos;, z = &apos;b&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;-- x = nil, y = nil&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;-- x = &apos;a&apos;, y = nil&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;             &lt;span class=&quot;c1&quot;&gt;-- x = &apos;a&apos;, y = &apos;b&apos;, z = nil&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;-- x = &apos;a&apos;, y = 20&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;-- x = &apos;nil&apos;, y = 20, 30 被丢弃&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;table&quot;&gt;Table&lt;/h3&gt;

&lt;p&gt;Lua 中的 Table 是一个 Key-value 的数据结构，它很像 Javascript 中的 Object，或是 PHP 中的数组。&lt;/p&gt;

&lt;h4 id=&quot;基本操作&quot;&gt;基本操作&lt;/h4&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;tbl1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- 初始化一个空表&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tbl2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;yuxm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tbl3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;yuxm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tbl2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;zhangsan&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tbl3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lisi&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tbl3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;数组&quot;&gt;数组&lt;/h4&gt;

&lt;p&gt;初始化一个数组:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以直接通过构造函数初始化:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;lua 的数组可以不限制元素类型, 以下脚本最终输出&lt;code&gt;nihao&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#! /usr/local/bin/lua&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;abc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;def&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;456&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nihao&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是，lua 中数组的索引下标从 1 开始，这与其他的语言不一致.&lt;/p&gt;

&lt;p&gt;Lua中的变量，如果没有local关键字，全都是全局变量，Lua也是用Table来管理全局变量的，Lua把这些全局变量放在了一个叫 “_G” 的 Table 里。例如，下面代码打印在当前环境中所有的全局变量的名字：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过如下方式访问全局变量:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;globalvar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;globalvar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- output: 123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;metatable-和-metamethod&quot;&gt;MetaTable 和 MetaMethod&lt;/h3&gt;

&lt;p&gt;我们可以对 Lua 中 table 的 key-value 执行加操作，访问 key 对应的 value，遍历所有的 key-value; 但是我们不可以对两个 table 执行加操作，也不可以比较两个表的大小。&lt;/p&gt;

&lt;p&gt;Metatables 允许我们改变 table 的行为，例如使用 Metatables 我们可以定义 Lua 如何计算两个 table 的相加操作。当 Lua 试图对两个表进行相加时，他会检查两个表是否有一个表有 Metatable，并且检查 Metatable 是否有 __add 域。如果找到则调用这个 __add 函数（所谓的Metamethod）去计算结果。&lt;/p&gt;

&lt;p&gt;Lua 中的每一个表都有其 Metatable，Lua 默认创建一个不带 metatable 的新表:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;tbl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;getmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tbl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;-- output: nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;可以使用setmetatable函数设置或者改变一个表的metatable&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;tbl1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tbl2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;setmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tbl1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tbl2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;getmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tbl1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tbl2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;-- true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;假如现在有两个 table, 分别为描述两个矩形, 分别计算两个矩形的长和宽之和:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;square1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;square2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;square_op&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;square_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;__add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;setmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;square1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;square_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;square&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;square1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;square2&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;total length: &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;, total width: &apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;total length: 220, total width: 170
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似 __add 这样的 MetaMethod，是 Lua 内建约定的，其它的还有如下的 MetaMethod：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;__add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__mod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__unm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                        &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                  &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                        &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                      &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                      &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__le&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                      &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                   &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__newindex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;             &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                  &lt;span class=&quot;err&quot;&gt;对应表达式&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;面向对象&quot;&gt;面向对象&lt;/h3&gt;

&lt;p&gt;Lua 不存在类的概念，可以通过对 __index 的重载来定义对象及行为。所谓__index，说得明确一点，如果我们有两个对象a和b，我们想让b作为a的prototype只需要：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;setmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下为一个操作面向对象的例子：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;yuxm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;setmetatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; : &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;me&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;me&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;-- yuxm : 33&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;kf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xiaoqiang&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;-- xiaoqiang : 12&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;模块&quot;&gt;模块&lt;/h3&gt;

&lt;p&gt;可以直接使用 &lt;code&gt;require(&quot;model_name&quot;)&lt;/code&gt;来载入别的 lua 文件，载入的时候就直接执行那个文件了。比如：
我们有一个hello.lua的文件：&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果我们&lt;code&gt;require(“hello”)&lt;/code&gt;，那么就直接输出 &lt;code&gt;Hello, World！&lt;/code&gt;了。&lt;/p&gt;

&lt;p&gt;注意：
1）require 函数，载入同样的 lua 文件时，只有第一次的时候会去执行，后面的相同的都不执行了。
2）如果你要让每一次文件都会执行的话，你可以使用 dofile(“hello”) 函数
3）如果你要玩载入后不执行，等你需要的时候执行时，你可以使用 loadfile()函数，如下所示：&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;loadfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;loadfile(“hello”)后，文件并不执行，我们把文件赋给一个变量hello，当hello()时，才真的执行。&lt;/p&gt;

&lt;h3 id=&quot;参考资料&quot;&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.lua.org/manual/5.3/&quot;&gt;Lua 5.3 Reference Manual&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.codingnow.com/2000/download/lua_manual.html&quot;&gt;Lua 5.1 参考手册&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://coolshell.cn/articles/10739.html&quot;&gt;Lua 简明教程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://manual.luaer.cn/&quot;&gt;Lua 在线手册&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://book.luaer.cn/&quot;&gt;Lua 程序设计&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 07 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/07/07/lua-learning-tutorial/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/07/07/lua-learning-tutorial/</guid>
        
        <category>编程语言</category>
        
        <category>Lua</category>
        
        
      </item>
    
      <item>
        <title>分布式事务</title>
        <description>&lt;p&gt;分布式事务是几乎每一个分布式系统中都会涉及到的一个技术问题，特别如今微服务架构被广泛应用在各企业的开发场景中。本文对分布式事务相关学习进行整理归纳，做为后续学习参考，其间参考和引用了部分网络和书籍资料，都很有借鉴价值，特在每篇文章末尾的参考资料环节中附注。&lt;/p&gt;

&lt;h1 id=&quot;本地事务&quot;&gt;本地事务&lt;/h1&gt;

&lt;p&gt;在过去很多年的开发过程中，基于数据库的事务一直被广大开发人员和数据库管理与使用人员所熟知。广义上事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元，狭义上事务在某种情况下特指数据库事务。&lt;/p&gt;

&lt;h2 id=&quot;acid&quot;&gt;ACID&lt;/h2&gt;

&lt;p&gt;事务具有四个特征，分别是原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation) 和持久性 (Durabilily)，简称事务的 ACID 属性。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原子性 (Atomicity)&lt;/strong&gt;： 指事务必须是一个原子的操作序列单元，事务中包含的各项操作在一次执行过程中，要么全部执行成功，要么全部执行失败；&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一致性 (Consistency)&lt;/strong&gt;： 指事务必须是使数据库从一个一致性状态变到另一个一致性状态，事务的中间状态不能被观察到的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;隔离性 (Isolation)&lt;/strong&gt;： 指多个事务并发执行的时候，事务内部的操作与其他事务是隔离的，并发执行的各个事务之间不能互相干扰。 隔离性又分为四个级别：读未提交(read uncommitted)、读已提交(read committed)、可重复读(repeatable read)、串行化(serializable)。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;持久性 (Durabilily)&lt;/strong&gt;: 指事务一旦提交，它对数据库中对应数据的状态变更应该是永久的，后续其他操作或故障不应该对其有任何影响。&lt;/p&gt;

&lt;h2 id=&quot;隔离级别&quot;&gt;隔离级别&lt;/h2&gt;

&lt;p&gt;标准 SQL 规范中定义了 4 个事务的隔离级别，不同的隔离级别对事务的处理相应不同。分别为：读未提交 (Read uncommitted)、读已提交 (Read committed)、可重复读 (Repeatable read) 和串行化 (Serializable)。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;读未提交&lt;/strong&gt;：最低的隔离级别。允许 “脏读” (Dirty Reads)，事务可以看到其他事务 “尚未提交” 的修改。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;读已提交&lt;/strong&gt;: 基于锁机制并发控制的 DBMS 需要对选定对象的写锁一直保持到事务结束，但是读锁在 SELECT 操作完成后马上释放。解决了 “脏读” 的问题，但是会存在 “不可重复读” 的问题 (Nonrepeatable Reads)。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;可重复读&lt;/strong&gt;：基于锁机制并发控制的 DBMS 需要对选定对象的读锁和写锁一直保持到事务结束，但不要求“范围锁”。解决了 “不可重复读” 的问题，但是可能会发生 “幻读” (Phantoms)。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;串行化&lt;/strong&gt;：在基于锁机制并发控制的 DBMS 实现可串行化，要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。可以避免 “幻读”（Phantoms）现象。&lt;/p&gt;

&lt;p&gt;对于其中涉及的几种读现象，解释如下：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;脏读&lt;/strong&gt;：当一个事务允许读取另外一个事务修改但未提交的数据时，就可能发生脏读。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/attachment/distributed-transaction/8F322917A3D82C246A3960D1A212EA33.jpg&quot; alt=&quot;dirty-read&quot; /&gt;&lt;/p&gt;

&lt;p&gt;事务 T1 首先从表中查询 id=1 的字段 (value = a)，然后另外一个事务 T2 更新表中 id=1 的字段 (value = b), 但是此时 T2 未提交事务，T1查询到的为事务 T2 更新的值 b，后事务 T2 回滚事务，T1 查询到的还是 T2 更新的值 b，发生数据不一致。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;不可重复读&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/attachment/distributed-transaction/36c0727913bcc31bc43bb49cc285e20a.jpg&quot; alt=&quot;nonrepeatable-reads&quot; /&gt;&lt;/p&gt;

&lt;p&gt;事务 T1 首先从表中查询 id=1 的行，然后另外一个事务 T2 更新表中 id=1 的字段值为 b 并且提交事务, 这时 T1 看到的仍然是它之前查询到的结果，在此执行 select 是仍然和之前的查询结果一样。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;幻读&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/attachment/distributed-transaction/b80ff76ca06076a113586b2f5fbe35c0.png&quot; alt=&quot;phantoms-read&quot; /&gt;&lt;/p&gt;

&lt;p&gt;事务 T1 首先从表中查询 id 从 1 到 20 的行，然后另外一个事务 T2 往表插入了 id=3 的行并且提交事务, 这时 T1 看到的结果集仍然和它之前查询到的结果集一样。&lt;/p&gt;

&lt;p&gt;关于事务的隔离级别，可以归结为下表：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;隔离级别&lt;/th&gt;
      &lt;th&gt;脏读&lt;/th&gt;
      &lt;th&gt;不可重复读&lt;/th&gt;
      &lt;th&gt;幻读&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;读未提交&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;读已提交&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;可重复读&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
      &lt;td&gt;Y&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;串行化&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
      &lt;td&gt;N&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;分布式理论&quot;&gt;分布式理论&lt;/h1&gt;

&lt;p&gt;在单机数据库中，实现一套满足 ACID 事务特性的事务处理系统相对比较容易，但是在分布式场景下，数据分散在不同的机器上，如何对这些数据进行分布式的事务处理具有非常大的挑战。分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上，通过一个分布式事务，往往会涉及对多个数据源或业务系统的操作。&lt;/p&gt;

&lt;p&gt;于是，伴随着分布式系统和分布式理论的发展，出现了 CAP 和 BASE 这样的分布式系统经典理论。CAP 理论指分布式系统不可能同时满足一致性（C：Consistency）、可用性（A：Availability）和分区容忍性（P：Partition tolerance），最多只能同时满足其中两项。相对应的，BASE 理论是基本可用 (Basically Available)、软状态 (Soft state) 和最终一致性 (Eventually consistent) 三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果，是基于 CAP 定理逐步演化而来的。&lt;/p&gt;

&lt;p&gt;有关 CAP 和 BASE 理论的讨论可以参见之前的文章 &lt;a href=&quot;http://www.yuxiumin.com/2017/04/22/cap-base-flp/&quot;&gt;分布式系统CAP理论和BASE思想概述&lt;/a&gt;, 这里不做过多描述。&lt;/p&gt;

&lt;h1 id=&quot;分布式事务&quot;&gt;分布式事务&lt;/h1&gt;

&lt;p&gt;在分布式系统中，实现分布式事务主要可以通过以下几种方式：&lt;/p&gt;

&lt;h2 id=&quot;两阶段提交&quot;&gt;两阶段提交&lt;/h2&gt;

&lt;p&gt;两阶段提交协议也被认为是一种一致性协议，在分布式场景下，能够方便的完成所有分布式事务参与者的协调，统一决定事务的提交或混滚，从而有效保证事务的分布式数据一致性。&lt;/p&gt;

&lt;p&gt;有关两阶段提交的讨论可以参考之前的文章 &lt;a href=&quot;http://www.yuxiumin.com/2017/05/08/two-phase-commit-protocol/&quot;&gt;两阶段协议&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;三阶段提交&quot;&gt;三阶段提交&lt;/h2&gt;

&lt;p&gt;三阶段提交协议是两阶段提交协议的改进版，将两阶段提交协议的“提交阶段”进行拆分，形成了由 CanCommit、PreCommit 和 doCommit 三个阶段组成的事务处理协议。&lt;/p&gt;

&lt;p&gt;有关两阶段提交的讨论可以参考之前的文章 &lt;a href=&quot;http://www.yuxiumin.com/2017/05/10/three-phase-commit-protocol/&quot;&gt;三阶段协议&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;tcc&quot;&gt;TCC&lt;/h2&gt;

&lt;p&gt;TCC (Try-Confirm-Cancel) 分布式模型相较于 XA 等基于 2PC 实现的事务模型，特征在于不依赖于资源管理器对分布式事务的支持，而是通过对业务逻辑的分解来实现分布式事务。&lt;/p&gt;

&lt;p&gt;TCC 模型认为对于业务系统中的一个特定的业务逻辑，其对外提供服务时，必须接受一些不确定性，即对业务逻辑初步操作的调用仅是一个临时性操作，调用它的主业务服务保留了后续的取消权。如果主业务服务认为全局事务应该回滚，它会要求取消之前的临时操作，这就对应从业务服务的取消操作。而当主业务服务认为全局事务应该提交时，它会放弃之前临时性操作的取消权，这对应从业务服务的确认操作。每一个初步操作，最终都会被确认或取消。&lt;/p&gt;

&lt;p&gt;因此，针对一个具体的业务服务，TCC 分布式事务模型需要业务系统提供三段业务逻辑：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;初步操作 Try: 完成所有业务检查，预留必须的资源；&lt;/li&gt;
  &lt;li&gt;确认操作 Confirm: 真正执行业务逻辑，不做任务业务检查，只使用 Try 阶段预留的业务资源。因此，只要 Try 成功，Confirm 必须能成功。另外，Confirm操作需满足幂等性，保证每一笔分布式事务有且只能成功一次。&lt;/li&gt;
  &lt;li&gt;取消操作 Cancel: 释放 Try 阶段预留的业务资源。同样，Cancel 操作也需要保证幂等性。
&lt;img src=&quot;/assets/attachment/distributed-transaction/91762cceea0e9be0251923dfaa809686.jpg&quot; alt=&quot;tcc-transaction-model&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCC 模型包括三部分：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主业务服务：整个业务活动的发起方，服务的编排者，负责发起并完成整个业务活动；&lt;/li&gt;
  &lt;li&gt;从业务服务：整个业务活动的参与方，负责提供 TCC 业务操作，实现初步操作 (Try)、确认操作 (Confirm)、取消操作 (Cancel）三个接口，供主业务服务调用;&lt;/li&gt;
  &lt;li&gt;业务活动管理器：管理控制整个业务活动，包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态，并在业务活动提交时调用所有从业务服务的 Confirm操作, 在业务活动取消时调用所有从业务服务的 Cancel 操作。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCC 模型流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主业务服务开启本地事务；&lt;/li&gt;
  &lt;li&gt;主业务服务向业务活动管理器申请启动分布式事务主业务活动;&lt;/li&gt;
  &lt;li&gt;针对要调用的从业务服务，主业务活动先向活动管理器注册从业务活动，然后调用从业务活动的 Try 接口；&lt;/li&gt;
  &lt;li&gt;当所有从业务服务的 Try 接口调用成功，主业务服务提交本地请求；若调用失败，主业务服务回滚本地事务；&lt;/li&gt;
  &lt;li&gt;若从业务服务提交本地事务，则 TCC 模型分别调用所有从业务服务的 Confirm 接口；若主业务服务回滚本地事务，则分别调用 Cancel 接口；&lt;/li&gt;
  &lt;li&gt;所有从业务服务的 Confirm 或 Cancel 操作完成后，全局事务结束。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCC 模型的原子性：TCC 模型可以看做 2PC 的一种变种，Try 操作对应 2PC 的一阶段 Prepare；Confirm 对应 2PC 的二阶段 Commit，Cancel 对应 2PC 的二阶段 Rollback，可以说 TCC 就是应用层的 2PC；&lt;/p&gt;

&lt;p&gt;TCC 模型的隔离性：与 2PC 不同，2PC 要求一直持有锁直到第二阶段执行结束，会导致性能下降；TCC 模型的隔离性思想是通过业务的改造，在第一阶段结束之后，将从底层数据库资源层面的加锁过渡为上层业务层面的加锁，从而释放底层数据库锁资源，放宽分布式事务锁协议，提高业务并发性能。&lt;/p&gt;

&lt;p&gt;TCC 模型的一致性：与 2PC 相同的是也是通过原子性保证事务的原子提交、业务隔离性控制事务的并发访问，实现分布式事务的一致性状态转变；但是在 TCC 模型中，事务的中间状态不能被观察到，会出现短暂的不一致，但是最终会达到一致性的状态，这符合 BASE 理论，也是柔性事务的提现。&lt;/p&gt;

&lt;p&gt;以最常见的转账业务举例，假设用户张三向用户李四进行转账，这里把转账业务拆分为交易服务和账务服务两个分布式事务，交易服务作为主业务服务，账务服务作为从业务服务：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;交易服务首先开启本地事务;&lt;/li&gt;
  &lt;li&gt;交易服务向业务活动管理器申请启动分布式事务主业务活动；&lt;/li&gt;
  &lt;li&gt;交易服务向活动管理器注册账务服务，调用账务服务的 Try 接口，对用户张三的资金进行冻结；&lt;/li&gt;
  &lt;li&gt;账务服务的 Try 接口调用成功，交易服务提交本地请求;&lt;/li&gt;
  &lt;li&gt;账务服务提交本地事务, 调用账务服务的 Confirm 接口扣除用户张三的预冻结资金，增加用户李四的可用资金;&lt;/li&gt;
  &lt;li&gt;全局事务结束&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;异步确保模型&quot;&gt;异步确保模型&lt;/h2&gt;
&lt;p&gt;异步确保模型也叫本地消息表模型，由 ebay 最早提出，后来在电商领域被大范围使用。基本思路是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;主业务服务增加消息记录表，维护待发送消息记录；从业务服务增加消息记录表，维护被处理的消息记录；其中主业务服务逻辑和主业务服务待发送消息记录在一个事务，从业务服务逻辑和从业务服务被处理的消息在一个事务；&lt;/li&gt;
  &lt;li&gt;主业务服务处理业务逻辑，并向待发送消息记录表增加消息记录，主业务逻辑和消息记录在一个事务；&lt;/li&gt;
  &lt;li&gt;主业务服务轮询消息记录表，发送消息到 MQ； 如果出现发送失败的情况，需要进行重试，所以从业务服务需保证幂等性；&lt;/li&gt;
  &lt;li&gt;从业务服务订阅 MQ 消息，向待处理消息记录表增加 messageId 记录；&lt;/li&gt;
  &lt;li&gt;从业务服务处理业务逻辑，处理成功后删除消息记录表中记录；向主业务服务发送 MQ 消息，通知主 MQ.&lt;/li&gt;
  &lt;li&gt;从业务轮询本地消息记录表，如果有未处理消息，则补处理后发送给主业务服务；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;与 TCC 类似，异步确保模型也遵循 BASE 理论，不过在实现上更为简单，通过数据库和消息的方式即可实现。不过，相对而言对业务代码侵入性比较强，也可能存在一定的延时发生。&lt;/p&gt;

&lt;h2 id=&quot;最大努力通知型&quot;&gt;最大努力通知型&lt;/h2&gt;

&lt;p&gt;最大努力通知型主要也是借助 MQ 消息系统来进行事务控制，实现也比较简单，它本质上就是通过定期校对，实现数据一致性。基本思路是：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;业务活动的主动方，在完成业务处理之后，向业务活动的被动方发送消息，允许消息丢失。&lt;/li&gt;
  &lt;li&gt;主动方可以设置时间阶梯型通知规则，在通知失败后按规则重复通知，直到通知N次后不再通知。&lt;/li&gt;
  &lt;li&gt;主动方提供校对查询接口给被动方按需校对查询，用于恢复丢失的业务消息。&lt;/li&gt;
  &lt;li&gt;业务活动的被动方如果正常接收了数据，就正常返回响应，并结束事务。&lt;/li&gt;
  &lt;li&gt;如果被动方没有正常接收，根据定时策略，向业务活动主动方查询，恢复丢失的业务消息。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最大努力通知型被动方的处理结果不影响主动方的处理结果，适用于对业务最终一致性的时间敏感度低的系统和适合跨企业的系统间的操作，或者企业内部比较独立的系统间的操作；&lt;/p&gt;

&lt;h3 id=&quot;小结&quot;&gt;小结&lt;/h3&gt;

&lt;p&gt;以上是个人对分布式事务的一些总结，在分布式领域目前被应用最多的应该还是基于 2PC 的 XA 事务和 TCC 事务，而异步确保和最大努力通知型事务，通过 MQ 的消息传递来将大事务化解为本地小事务，方法也非常巧妙，最终还是取决于业务场景的选择。&lt;/p&gt;

&lt;h3 id=&quot;参考资料&quot;&gt;参考资料&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://queue.acm.org/detail.cfm?id=1394128&quot;&gt;Base: An Acid Alternative&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.tianshouzhi.com/api/tutorials/distributed_transaction/383&quot;&gt;分布式事务概述&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html&quot;&gt;聊聊分布式事务，再说说解决方案&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Isolation_(database_systems)&quot;&gt;Isolation (database systems)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/11043712/what-is-the-difference-between-non-repeatable-read-and-phantom-read&quot;&gt;What is the difference between Non-Repeatable Read and Phantom Read?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/RDnf637MY0IVgv2NpNVByw&quot;&gt;一篇文章带你学习分布式事务-蚂蚁金服科技&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html&quot;&gt;聊分布式事务，再说说解决方案&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 25 Jun 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/06/25/distributed-transaction/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/06/25/distributed-transaction/</guid>
        
        <category>分布式</category>
        
        <category>事务</category>
        
        
      </item>
    
      <item>
        <title>Redis 学习笔记 - 哨兵</title>
        <description>&lt;p&gt;Redis 主从复制模式下，一旦主节点由于故障不能提供服务，需要人工将从节点晋升为主节点，同时还要通知应用方更新主节点地址，对于很多应用场景这种故障处理的方式是无法接受的。Redis 从 2.8 开始正式提供了 Redis Sentinel（哨兵）架构来解决这个问题，本文对于 Redis Sentinel 做出简单介绍。&lt;/p&gt;

&lt;h1 id=&quot;基本概念&quot;&gt;基本概念&lt;/h1&gt;
&lt;h2 id=&quot;主从复制的问题&quot;&gt;主从复制的问题&lt;/h2&gt;
&lt;p&gt;Redis 主从复制模式可以将主节点的数据改变同步给从节点，这样从节点就可以起到两个作用：第一，作为主节点的一个备份，一旦主节点出了故障不可达的情况，从节点可以作为后备提供服务，保证数据尽量不丢失（主从复制是最终一致性）。第二，从节点可以扩展主节点的读能力，一旦主节点不能支撑住大并发量的读操作，从节点可以在一定程度上帮助主节点分担读压力。&lt;/p&gt;

&lt;p&gt;但是主从复制也带来了以下问题：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;一旦主节点出现故障，需要手动将一个从节点晋升为主节点，同时需要修改应用方的主节点地址，还需要命令其他从节点去复制新的主节点，整个过程都需要人工干预。&lt;/li&gt;
  &lt;li&gt;主节点的写能力受到单机的限制。&lt;/li&gt;
  &lt;li&gt;主节点的存储能力受到单机的限制。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;高可用&quot;&gt;高可用&lt;/h2&gt;
&lt;p&gt;Redis 主从复制模式下，一旦主节点出现了故障不可达，需要人工干预，故障转移实时性和准确性上都无法得到保障。Redis主从复制模式下的主节点出现故障后，进行故障转移的过程如下所示：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主节点发生故障后，客户端 (client) 连接主节点失败，两个从节点与主节点连接失败造成复制中断；&lt;/li&gt;
  &lt;li&gt;如果主节点无法正常启动，需要选出一个从节点 (slave-1)，对其执行 &lt;code&gt;slaveof no one&lt;/code&gt; 命令使其成为新的主节点；&lt;/li&gt;
  &lt;li&gt;原来的从节点（slave-1）成为新的主节点后，更新应用方的主节点信息，重新启动应用方;&lt;/li&gt;
  &lt;li&gt;客户端命令另一个从节点（slave-2）去复制新的主节点（new-master）;&lt;/li&gt;
  &lt;li&gt;待原来的主节点恢复后，让它去复制新的主节点。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;redis-sentinel-的高可用性&quot;&gt;Redis Sentinel 的高可用性&lt;/h2&gt;
&lt;p&gt;当主节点出现故障时，Redis Sentinel 能自动完成故障发现和故障转移，并通知应用方，从而实现真正的高可用。&lt;/p&gt;

&lt;p&gt;Redis Sentinel 是一个分布式架构，其中包含若干个 Sentinel 节点和 Redis 数据节点，每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控，当它发现节点不可达时，会对节点做下线标识。如果被标识的是主节点，它还会和其他 Sentinel 节点进行“协商”，当大多数 Sentinel 节点都认为主节点不可达时，它们会选举出一个 Sentinel 节点来完成自动故障转移的工作，同时会将这个变化实时通知给 Redis 应用方。整个过程完全是自动的，不需要人工来介入，所以这套方案很有效地解决了 Redis 的高可用问题。&lt;/p&gt;

&lt;p&gt;从逻辑架构上看，Sentinel 节点集合会定期对所有节点进行监控，特别是对主节点的故障实现自动转移。整个故障转移的处理逻辑有下面4个步骤：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主节点出现故障，此时两个从节点与主节点失去连接，主从复制失败；&lt;/li&gt;
  &lt;li&gt;每个 Sentinel 节点通过定期监控发现主节点出现了故障；&lt;/li&gt;
  &lt;li&gt;多个 Sentinel 节点对主节点的故障达成一致，选举出其中一个 Sentinel 节点作为领导者负责故障转移；&lt;/li&gt;
  &lt;li&gt;Sentinel 领导者节点执行了故障转移，整个过程和主从复制的场景下是完全一致的，只不过是自动化完成的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Redis Sentinel 具有以下几个功能：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;监控：Sentinel 节点会定期检测 Redis 数据节点、其余 Sentinel 节点是否可达；&lt;/li&gt;
  &lt;li&gt;通知：Sentinel 节点会将故障转移的结果通知给应用方；&lt;/li&gt;
  &lt;li&gt;主节点故障转移：实现从节点晋升为主节点并维护后续正确的主从关系；&lt;/li&gt;
  &lt;li&gt;配置提供者：在 Redis Sentinel 结构中，客户端在初始化的时候连接的是 Sentinel 节点集合，从中获取主节点信息。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;实现原理&quot;&gt;实现原理&lt;/h1&gt;
&lt;h2 id=&quot;节点发现&quot;&gt;节点发现&lt;/h2&gt;
&lt;p&gt;Redis Sentinel 通过三个定时监控任务完成对各个节点发现和监控：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;每隔 10 秒，每个 Sentinel 节点会向主节点和从节点发送 info 命令获取最新的拓扑结构;&lt;/li&gt;
  &lt;li&gt;每隔 2 秒，每个 Sentinel 节点会向 Redis 数据节点的 &lt;code&gt;__sentinel__:hello&lt;/code&gt; 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息，同时每个 Sentinel 节点也会订阅该频道，来了解其他 Sentinel 节点以及它们对主节点的判断；&lt;/li&gt;
  &lt;li&gt;每隔 1 秒，每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条 ping 命令做一次心跳检测，来确认这些节点当前是否可达。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;节点选举&quot;&gt;节点选举&lt;/h2&gt;
&lt;p&gt;Redis Sentinel 使用了 Raft 算法实现领导者选举，大致思路如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;每个在线的 Sentinel 节点都有资格成为领导者，当它确认主节点主观下线时候，会向其他 Sentinel 节点发送 &lt;code&gt;sentinel is-master-down-by-addr&lt;/code&gt; 命令，要求将自己设置为领导者。&lt;/li&gt;
  &lt;li&gt;收到命令的 Sentinel 节点，如果没有同意过其他 Sentinel 节点的 &lt;code&gt;sentinel is-master-down-by-addr&lt;/code&gt; 命令，将同意该请求，否则拒绝;&lt;/li&gt;
  &lt;li&gt;如果该 Sentinel 节点发现自己的票数已经 &lt;code&gt; &amp;gt;= max(quorum, num(sentinels) / 2 + 1)&lt;/code&gt;，那么它将成为领导者;&lt;/li&gt;
  &lt;li&gt;如果此过程没有选举出领导者，将进入下一次选举。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;故障转移&quot;&gt;故障转移&lt;/h2&gt;
&lt;p&gt;领导者选举出的 Sentinel 节点负责故障转移，具体步骤如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;在从节点列表中选出一个节点作为新的主节点，选择方法如下：
    &lt;ul&gt;
      &lt;li&gt;过滤：“不健康”（主观下线、断线）、5 秒内没有回复过 Sentinel 节点 ping 响应、与主节点失联超过 &lt;code&gt;down-after-milliseconds*10&lt;/code&gt; 秒;&lt;/li&gt;
      &lt;li&gt;选择 slave-priority (从节点优先级) 最高的从节点列表，如果存在则返回，不存在则继续;&lt;/li&gt;
      &lt;li&gt;选择复制偏移量最大的从节点(复制的最完整)，如果存在则返回，不存在则继续;&lt;/li&gt;
      &lt;li&gt;选择 runid 最小的从节点;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Sentinel 领导者节点会对第一步选出来的从节点执行 &lt;code&gt;slaveof no one&lt;/code&gt; 命令让其成为主节点;&lt;/li&gt;
  &lt;li&gt;Sentinel 领导者节点会向剩余的从节点发送命令，让它们成为新主节点的从节点，复制规则和 parallel-syncs 参数有关;&lt;/li&gt;
  &lt;li&gt;Sentinel 节点集合会将原来的主节点更新为从节点，并保持着对其关注，当其恢复后命令它去复制新的主节点。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://redis.io/topics/sentinel&quot;&gt;sentinel&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 19 Apr 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/04/19/redis-learning-notes-sentinel/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/04/19/redis-learning-notes-sentinel/</guid>
        
        <category>Redis</category>
        
        <category>缓存</category>
        
        
      </item>
    
      <item>
        <title>Redis 学习笔记 - 复制</title>
        <description>&lt;p&gt;在分布式系统中为了解决单点问题，通常会把数据复制多个副本部署到其他机器，满足故障恢复和负载均衡等需求, Redis也是如此，它为我们提供了复制功能，实现了相同数据的多个 Redis 副本。复制功能是高可用 Redis 的基础，后面章节的哨兵和集群都是在复制的基础上实现高可用的。&lt;/p&gt;

&lt;h1 id=&quot;配置&quot;&gt;配置&lt;/h1&gt;
&lt;h2 id=&quot;建立复制&quot;&gt;建立复制&lt;/h2&gt;
&lt;p&gt;参与复制的 Redis 实例划分为主节点 (master) 和从节点 (slave)。默认情况下，Redis 都是主节点。每个从节点只能有一个主节点，而主节点可以同时具有多个从节点。复制的数据流是单向的，只能由主节点复制到从节点。配置复制的方式有以下三种：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;在配置文件中加入 &lt;code&gt;slaveof {masterHost} {masterPort}&lt;/code&gt; 随Redis启动生效。&lt;/li&gt;
  &lt;li&gt;在 redis-server 启动命令后加入 &lt;code&gt;-- slaveof {masterHost} {masterPort}&lt;/code&gt; 生效。&lt;/li&gt;
  &lt;li&gt;直接使用命令：&lt;code&gt;slaveof {masterHost} {masterPort}&lt;/code&gt; 生效。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;主从节点复制成功建立后，可以使用 &lt;code&gt;info replication&lt;/code&gt; 命令查看复制相关状态&lt;/p&gt;

&lt;h2 id=&quot;断开复制&quot;&gt;断开复制&lt;/h2&gt;
&lt;p&gt;slaveof 命令不但可以建立复制，还可以在从节点执行 &lt;code&gt;slaveof no one&lt;/code&gt; 来断开与主节点复制关系。断开复制主要流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;断开与主节点复制关系；&lt;/li&gt;
  &lt;li&gt;从节点晋升为主节点；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;从节点断开复制后并不会抛弃原有数据，只是无法再获取主节点上的数据变化。&lt;/p&gt;

&lt;p&gt;通过 slaveof 命令还可以实现切主操作(把当前从节点对主节点的复制切换到另一个主节点)。执行 &lt;code&gt;slaveof {newMasterIp} {newMasterPort}&lt;/code&gt; 命令即可，切主操作流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;断开与旧主节点复制关系。&lt;/li&gt;
  &lt;li&gt;与新主节点建立复制关系。&lt;/li&gt;
  &lt;li&gt;删除从节点当前所有数据。&lt;/li&gt;
  &lt;li&gt;对新主节点进行复制操作。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;只读性&quot;&gt;只读性&lt;/h2&gt;
&lt;p&gt;默认情况下，从节点使用 &lt;code&gt;slave-read-only=yes&lt;/code&gt; 配置为只读模式。&lt;/p&gt;

&lt;p&gt;由于复制只能从主节点到从节点，对于从节点的任何修改主节点都无法感知，修改从节点会造成主从数据不一致，因此建议线上不要修改从节点的只读模式。&lt;/p&gt;

&lt;h2 id=&quot;传输延迟&quot;&gt;传输延迟&lt;/h2&gt;
&lt;p&gt;主从节点一般部署在不同机器上，复制时的网络延迟就成为需要考虑的问题，Redis 为我们提供了repl-disable-tcp-nodelay 参数用于控制是否关闭 TCP_NODELAY，默认关闭，说明如下：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;当关闭时，主节点产生的命令数据无论大小都会及时地发送给从节点，这样主从之间延迟会变小，但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景，如同机架或同机房部署。&lt;/li&gt;
  &lt;li&gt;当开启时，主节点会合并较小的 TCP 数据包从而节省带宽，默认发送时间间隔取决于 Linux 的内核，一般默认为 40 毫秒。这种配置节省了带宽但增大主从之间的延迟，适用于主从网络环境复杂或带宽紧张的场景，如跨机房部署。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;拓扑&quot;&gt;拓扑&lt;/h1&gt;
&lt;p&gt;Redis 的复制拓扑结构可以支持单层或多层复制关系，根据拓扑复杂性可以分为以下三种：一主一从、一主多从、树状主从结构：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;一主一从结构：最简单的复制拓扑结构，用于主节点出现宕机时从节点提供故障转移支持；&lt;/li&gt;
  &lt;li&gt;一主多从结构（又称为星形拓扑结构）：一主多从结构使得应用端可以利用多个从节点实现读写分离。对于读占比较大的场景，可以把读命令发送到从节点来分担主节点压力。&lt;/li&gt;
  &lt;li&gt;树状主从结构（又称为树状拓扑结构）：树状主从结构使得从节点不但可以复制主节点数据，同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层，可以有效降低主节点负载和需要传送给从节点的数据量。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;原理&quot;&gt;原理&lt;/h1&gt;

&lt;h2 id=&quot;复制过程&quot;&gt;复制过程&lt;/h2&gt;
&lt;p&gt;在从节点执行 slaveof 命令后，复制过程便开始运作，复制过程大致分为 6 个过程：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;保存主节点信息；&lt;/li&gt;
  &lt;li&gt;从节点内部通过每秒运行的定时任务维护复制相关逻辑，当定时任务发现存在新的主节点后，会尝试与该节点建立网络连接；从节点会建立一个 socket 套接字专门用于接受主节点发送的复制命令，如果从节点无法建立连接，定时任务会无限重试直到连接成功或者执行 &lt;code&gt;slaveof no one&lt;/code&gt; 取消复制。&lt;/li&gt;
  &lt;li&gt;发送 ping 命令。连接建立成功后从节点发送 ping 请求进行首次通信。如果发送 ping 命令后，从节点没有收到主节点的 pong 回复或者超时，比如网络超时或者主节点正在阻塞无法响应命令，从节点会断开复制连接，下次定时任务会发起重连。&lt;/li&gt;
  &lt;li&gt;权限验证。如果主节点设置了 requirepass 参数，则需要密码验证，从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证；如果验证失败复制将终止，从节点重新发起复制流程。&lt;/li&gt;
  &lt;li&gt;同步数据集。主从复制连接正常通信后，对于首次建立复制的场景，主节点会把持有的数据全部发送给从节点，这部分操作是耗时最长的步骤。&lt;/li&gt;
  &lt;li&gt;命令持续复制。当主节点把当前的数据同步给从节点后，便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点，保证主从数据一致性。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;数据同步&quot;&gt;数据同步&lt;/h2&gt;
&lt;p&gt;Redis 在 2.8 及以上版本使用 psync 命令完成主从数据同步, 同步过程分为：全量复制和部分复制。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;全量复制：一般用于初次复制场景，它会把主节点全部数据一次性发送给从节点，当数据量较大时，会对主从节点和网络造成很大的开销。Redis 早期支持的复制功能只有全量复制。&lt;/li&gt;
  &lt;li&gt;部分复制：用于处理在主从复制中因网络闪断等原因造成的数据丢失场景，当从节点再次连上主节点后，如果条件允许，主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据，可以有效避免全量复制的过高开销。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;psync 命令运行需要以下组件支持：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主从节点各自复制偏移量: 参与复制的主从节点都会维护自身复制偏移量。主节点在处理完写入命令后，会把命令的字节长度做累加记录; 从节点每秒钟上报自身的复制偏移量给主节点，因此主节点也会保存从节点的复制偏移量, 从节点在接收到主节点发送的命令后，也会累加记录自身的偏移量。通过对比主从节点的复制偏移量，可以判断主从节点数据是否一致。&lt;/li&gt;
  &lt;li&gt;主节点复制积压缓冲区: 复制积压缓冲区是保存在主节点上的一个固定长度的队列，默认大小为1MB，当主节点有连接的从节点时被创建，这时主节点响应写命令时，不但会把命令发送给从节点，还会写入复制积压缓冲区&lt;/li&gt;
  &lt;li&gt;主节点运行id: 每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。运行ID的主要作用是用来唯一识别Redis节点，比如从节点保存主节点的运行ID识别自己正在复制的是哪个主节点。&lt;/li&gt;
  &lt;li&gt;psync命令: 从节点使用psync命令完成部分复制和全量复制功能，命令格式：&lt;code&gt;psync {runId} {offset}&lt;/code&gt;，参数含义如下：
    &lt;ul&gt;
      &lt;li&gt;runId：从节点所复制主节点的运行id。&lt;/li&gt;
      &lt;li&gt;offset：当前从节点已复制的数据偏移量。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;psync 流程说明：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;从节点发送 psync 命令给主节点;&lt;/li&gt;
  &lt;li&gt;主节点根据 psync 参数和自身数据情况决定响应结果：
    &lt;ul&gt;
      &lt;li&gt;如果回复 &lt;code&gt;+FULLRESYNC {runId} {offset}&lt;/code&gt;，那么从节点将触发全量复制流程。&lt;/li&gt;
      &lt;li&gt;如果回复 &lt;code&gt;+CONTINUE&lt;/code&gt;，从节点将触发部分复制流程。&lt;/li&gt;
      &lt;li&gt;如果回复 &lt;code&gt;+ERR&lt;/code&gt;，说明主节点版本低于 Redis2.8，无法识别 psync 命令，从节点将发送旧版的 sync 命令触发全量复制流程。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;全量复制&quot;&gt;全量复制&lt;/h2&gt;
&lt;p&gt;全量复制是 Redis 最早支持的复制方式，也是主从第一次建立复制时必须经历的阶段。触发全量复制的命令是 sync 和 psync。全量复制流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;发送 psync 命令进行数据同步，由于是第一次进行复制，从节点没有复制偏移量和主节点的运行ID，所以发送 &lt;code&gt;psync-1&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;主节点根据 &lt;code&gt;psync-1&lt;/code&gt; 解析出当前为全量复制，回复 &lt;code&gt;+FULLRESYNC&lt;/code&gt; 响应;&lt;/li&gt;
  &lt;li&gt;从节点接收主节点的响应数据保存运行 ID 和偏移量 offset;&lt;/li&gt;
  &lt;li&gt;主节点执行 bgsave 保存 RDB 文件到本地;&lt;/li&gt;
  &lt;li&gt;主节点发送 RDB 文件给从节点，从节点把接收的 RDB 文件保存在本地并直接作为从节点的数据文件;&lt;/li&gt;
  &lt;li&gt;对于从节点开始接收 RDB 快照到接收完成期间，主节点仍然响应读写命令，因此主节点会把这期间写命令数据保存在复制客户端缓冲区内，当从节点加载完 RDB 文件后，主节点再把缓冲区内的数据发送给从节点，保证主从之间数据一致性。如果主节点创建和传输 RDB 的时间过长，对于高流量写入场景非常容易造成主节点复制客户端缓冲区溢出。默认配置为 &lt;code&gt;client-output-buffer-limit slave 256MB 64MB 60&lt;/code&gt;，如果 60 秒内缓冲区消耗持续大于 64MB 或者直接超过 256MB 时，主节点将直接关闭复制客户端连接，造成全量同步失败。&lt;/li&gt;
  &lt;li&gt;从节点接收完主节点传送来的全部数据后会清空自身旧数据;&lt;/li&gt;
  &lt;li&gt;从节点清空数据后开始加载 RDB 文件，对于较大的 RDB 文件，这一步操作依然比较耗时，可以通过计算日志之间的时间差来判断加载 RDB 的总耗时;&lt;/li&gt;
  &lt;li&gt;从节点成功加载完 RDB 后，如果当前节点开启了 AOF 持久化功能，它会立刻做 bgrewriteaof 操作，为了保证全量复制后AOF持久化文件立刻可用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;全量复制是一个非常耗时费力的操作，它的时间开销主要包括：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;主节点bgsave时间;&lt;/li&gt;
  &lt;li&gt;RDB文件网络传输时间;&lt;/li&gt;
  &lt;li&gt;从节点清空数据时间;&lt;/li&gt;
  &lt;li&gt;从节点加载RDB的时间;&lt;/li&gt;
  &lt;li&gt;可能的AOF重写时间;
所以除了第一次复制时采用全量复制在所难免之外，对于其他场景应该规避全量复制的发生。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;部分复制&quot;&gt;部分复制&lt;/h2&gt;
&lt;p&gt;部分复制主要是 Redis 针对全量复制的开销过高做出的一种优化措施，使用 &lt;code&gt;psync {runId} {offset}&lt;/code&gt; 命令实现。当从节点正在复制主节点时，如果出现网络闪断或者命令丢失等异常情况时，从节点会向主节点要求补发丢失的命令数据，如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点，这样就可以保持主从节点复制的一致性。部分复制的大致流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;当主从节点之间网络出现中断时，如果超过 repl-timeout时间，主节点会认为从节点故障并中断复制连接；&lt;/li&gt;
  &lt;li&gt;主从连接中断期间主节点依然响应命令，但因复制连接中断命令无法发送给从节点，不过主节点内部存在的复制积压缓冲区，依然可以保存最近一段时间的写命令数据，默认最大缓存 1MB。&lt;/li&gt;
  &lt;li&gt;当主从节点网络恢复后，从节点会再次连上主节点；&lt;/li&gt;
  &lt;li&gt;当主从连接恢复后，由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们当作 psync 参数发送给主节点，要求进行部分复制操作；&lt;/li&gt;
  &lt;li&gt;主节点接到 psync 命令后首先核对参数 runId 是否与自身一致，如果一致，说明之前复制的是当前主节点；之后根据参数 offset 在自身复制积压缓冲区查找，如果偏移量之后的数据存在缓冲区中，则对从节点发送 &lt;code&gt;+CONTINUE&lt;/code&gt; 响应，表示可以进行部分复制；&lt;/li&gt;
  &lt;li&gt;主节点根据偏移量把复制积压缓冲区里的数据发送给从节点，保证主从复制进入正常状态。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;异步复制&quot;&gt;异步复制&lt;/h2&gt;
&lt;p&gt;主节点不但负责数据读写，还负责把写命令同步给从节点。写命令的发送过程是异步完成，也就是说主节点自身处理完写命令后直接返回给客户端，并不等待从节点复制完成。主节点复制流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;主节点 6379 接收处理命令；&lt;/li&gt;
  &lt;li&gt;命令处理完之后返回响应结果；&lt;/li&gt;
  &lt;li&gt;对于修改命令异步发送给 6380 从节点，从节点在主线程中执行复制的命令。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;开发与运维中的问题&quot;&gt;开发与运维中的问题&lt;/h2&gt;

&lt;h3 id=&quot;读写分离&quot;&gt;读写分离&lt;/h3&gt;
&lt;p&gt;对于读占比较高的场景，可以通过把一部分读流量分摊到从节点来减轻主节点压力，同时需要注意永远只对主节点执行写操作。当使用从节点响应读请求时，业务端可能会遇到如下问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;数据延迟&lt;/strong&gt;: Redis 复制数据的延迟由于异步复制特性是无法避免的，延迟取决于网络带宽和命令阻塞情况，比如刚在主节点写入数据后立刻在从节点上读取可能获取不到。对于无法容忍大量延迟场景，可以编写外部监控程序监听主从节点的复制偏移量，当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;读到过期数据&lt;/strong&gt;: 当主节点存储大量设置超时的数据时，如缓存数据，Redis 内部需要维护过期数据删除策略，删除策略主要有两种：惰性删除和定时删除。
惰性删除指主节点每次处理读取命令时，都会检查键是否超时，如果超时则执行删除命令删除键对象，之后删除命令也会异步发送给从节点(需要注意的是为了保证复制的一致性，从节点自身永远不会主动删除超时数据)；定时删除指 Redis 主节点在内部定时任务会循环采样一定数量的键，当发现采样的键过期时执行删除命令，之后再同步给从节点；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;从节点故障&lt;/strong&gt;：对于从节点的故障问题，需要在客户端维护可用从节点列表，当从节点故障时立刻切换到其他从节点或主节点上。这个过程类似上文提到的针对延迟过高的监控处理，需要开发人员改造客户端类库。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;规避全量复制&quot;&gt;规避全量复制&lt;/h3&gt;
&lt;p&gt;以下是需要进行全量复制的场景：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;第一次建立复制：由于是第一次建立复制，从节点不包含任何主节点数据，因此必须进行全量复制才能完成数据同步；&lt;/li&gt;
  &lt;li&gt;节点运行ID不匹配：当主从复制关系建立后，从节点会保存主节点的运行 ID，如果此时主节点因故障重启，那么它的运行 ID 会改变，从节点发现主节点运行 ID 不匹配时，会认为自己复制的是一个新的主节点从而进行全量复制。&lt;/li&gt;
  &lt;li&gt;复制积压缓冲区不足：当主从节点网络中断后，从节点再次连上主节点时会发送 &lt;code&gt;psync {offset} {runId}&lt;/code&gt; 命令请求部分复制，如果请求的偏移量不在主节点的积压缓冲区内，则无法提供给从节点数据，因此部分复制会退化为全量复制。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除此之外，应尽量避免全量复制，采用部分复制。&lt;/p&gt;

&lt;h3 id=&quot;规避复制风暴&quot;&gt;规避复制风暴&lt;/h3&gt;
&lt;p&gt;复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。&lt;/p&gt;

&lt;p&gt;复制风暴会对发起复制的主节点造成大量开销，导致CPU、内存、带宽消耗，因此应该分析出复制风暴发生的场景，提前采用合理的方式规避。规避方式有如下几个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;单主节点复制风暴：单主节点复制风暴一般发生在主节点挂载多个从节点的场景。当主节点重启恢复后，从节点会发起全量复制流程，这时主节点就会为从节点创建 RDB 快照，如果在快照创建完毕之前，有多个从节点都尝试与主节点进行全量同步，那么其他从节点将共享这份RDB快照。解决方案首先可以减少主节点挂载从节点的数量，或者采用树状复制结构，加入中间层从节点用来保护主节点。&lt;/li&gt;
  &lt;li&gt;单机器复制风暴：由于 Redis 的单线程架构，通常单台机器会部署多个 Redis 实例。当一台机器上同时部署多个主节点时，如果这台机器出现故障或网络长时间中断，当它重启恢复后，会有大量从节点针对这台机器的主节点进行全量复制，会造成当前机器网络带宽耗尽。避免方法如下：
    &lt;ul&gt;
      &lt;li&gt;把主节点尽量分散在多台机器上，避免在单台机器上部署过多的主节点;&lt;/li&gt;
      &lt;li&gt;当主节点所在机器故障后提供故障转移机制，避免机器恢复后进行密集的全量复制。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://redis.io/topics/replication&quot;&gt;Replication&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 12 Apr 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/04/12/redis-learning-notes-replication/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/04/12/redis-learning-notes-replication/</guid>
        
        <category>Redis</category>
        
        <category>缓存</category>
        
        
      </item>
    
      <item>
        <title>Redis 学习笔记 - 持久化</title>
        <description>&lt;p&gt;上一篇文章介绍了 Redis 的基本命令操作，本文主要介绍 Redis 的另一个特性，持久化。&lt;/p&gt;

&lt;p&gt;Redis 支持 RDB 和 AOF 两种持久化机制，对内存中的数据进行持久化，可以有效地避免因进程退出造成的数据丢失问题，当下次重启时利用之前持久化的文件即可实现数据恢复&lt;/p&gt;

&lt;h1 id=&quot;rdb&quot;&gt;RDB&lt;/h1&gt;

&lt;p&gt;RDB 持久化把当前进程数据生成快照保存到文件的过程，&lt;/p&gt;

&lt;h2 id=&quot;优缺点&quot;&gt;优缺点&lt;/h2&gt;
&lt;p&gt;RDB 持久化方式的优点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;RDB 是一个紧凑压缩的二进制文件，代表 Redis 在某个时间点上的数据快照。适用于备份, 例如每 24 小时生成 RDB 文件归档，并且每 30 天保存 RDB 文件快照，这样可以方便恢复不同版本的数据；&lt;/li&gt;
  &lt;li&gt;RDB 适用于灾难恢复，压缩后的二进制文件便于传递至远程数据中心进行备份恢复；&lt;/li&gt;
  &lt;li&gt;RDB 方式采用 fork 子进程的方式进行写入操作，对于父进程不会造成长期阻塞，因此性能较好；&lt;/li&gt;
  &lt;li&gt;加载 RDB 恢复数据远远快于 AOF 的方式。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RDB 持久化方式的缺点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;RDB 方式数据没办法做到秒级持久化, 不能避免在服务器故障时丢失数据。因为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 每次运行都要执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作创建子进程，属于重量级操作，频繁执行成本过高, 可能会至少 5 分钟才保存一次 RDB 文件。在这种情况下，一旦发生故障停机，有可能会丢失分钟的数据。&lt;/li&gt;
  &lt;li&gt;因为每次保存 RDB 时都要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 出一个子进程，并由子进程来进行实际的持久化工作。在数据集比较庞大时, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 可能会比较耗时，造成服务器在短时间内停止处理客户端请求；虽然 AOF 重写也需要进行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt;, 但无论 AOF 重写的执行间隔有多长，数据的耐久性都不会有任何损失。&lt;/li&gt;
  &lt;li&gt;RDB 文件使用特定二进制格式保存，Redis 版本演进过程中有多个格式的 RDB 版本，存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;触发机制&quot;&gt;触发机制&lt;/h3&gt;
&lt;p&gt;RDB 持久化触发过程可分为手动触发和自动触发。&lt;/p&gt;

&lt;p&gt;手动触发 RDB 持久化可通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令进行：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt;: 阻塞当前 Redis 服务器，直到 RDB 过程完成为止，如果当前内存较大会造成长时间阻塞，线上环境不建议使用。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt;: Redis 进程执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作创建子进程，RDB 持久化过程由子进程负责，完成后自动结束。阻塞只发生在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 阶段，一般时间很短。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令是针对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt; 阻塞问题做的优化。因此目前 Redis 内部所有的涉及 RDB 的操作都采用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 的方式。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;自动触发 RDB 的持久化机制通常在以下场景：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt; 相关配置，如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save m n&lt;/code&gt; 表示 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt; 秒内数据集存在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; 次修改时，自动触发 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;如果从节点执行全量复制操作，主节点自动执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 生成 RDB 文件并发送给从节点;&lt;/li&gt;
  &lt;li&gt;执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug reload&lt;/code&gt; 命令重新加载 Redis 时，也会自动触发 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt; 操作;&lt;/li&gt;
  &lt;li&gt;默认情况下执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shutdown&lt;/code&gt; 命令时，如果没有开启 AOF 持久化功能则自动执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt;;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;bgsave-流程&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 流程&lt;/h3&gt;
&lt;p&gt;对于目前主流的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令，其执行流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令，Redis 父进程判断当前是否存在正在执行的子进程(如 RDB/AOF 子进程)，如果存在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令直接返回；&lt;/li&gt;
  &lt;li&gt;父进程执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作创建子进程，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作过程中父进程会阻塞；&lt;/li&gt;
  &lt;li&gt;父进程 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 完成后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 命令返回 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Background saving started&lt;/code&gt; 信息, 并不再阻塞父进程，可以继续响应其他命令；&lt;/li&gt;
  &lt;li&gt;子进程创建 RDB 文件，根据父进程内存生成临时快照文件, 完成后对原有文件进行原子替换；&lt;/li&gt;
  &lt;li&gt;子进程发送信号给父进程表示完成，父进程更新统计信息。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;二aof&quot;&gt;二、AOF&lt;/h2&gt;
&lt;p&gt;AOF（append only file）持久化：以独立日志的方式记录每次写命令，重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性，目前已经是 Redis 持久化的主流方式。&lt;/p&gt;

&lt;h3 id=&quot;优缺点-1&quot;&gt;优缺点&lt;/h3&gt;
&lt;p&gt;AOF 持久化方式的优点：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;使用 AOF 持久化会让 Redis 变得更加耐久，可以设置不同的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 策略。 AOF 的默认策略为每秒钟 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 一次，在这种配置下，Redis 仍然可以保持良好的性能，并且就算发生故障停机，也最多只会丢失一秒钟的数据。&lt;/li&gt;
  &lt;li&gt;AOF 文件是一个只进行追加操作的日志文件，因此对 AOF 文件的写入不需要进行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seek&lt;/code&gt;，即使日志因为某些原因而包含了未写入完整的命令，redis-check-aof 工具也可以轻易地修复这种问题。&lt;/li&gt;
  &lt;li&gt;Redis 可以在 AOF 文件体积变得过大时，自动地在后台对 AOF 进行重写，重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的，因为 Redis 在创建新 AOF 文件的过程中，会继续将命令追加到现有的 AOF 文件里面，即使重写过程中发生停机，现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕，Redis 就会从旧 AOF 文件切换到新 AOF 文件，并开始对新 AOF 文件进行追加操作。&lt;/li&gt;
  &lt;li&gt;AOF 文件有序地保存了对数据库执行的所有写入操作，这些写入操作以 Redis 协议的格式保存，因此 AOF 文件的内容非常容易被人读懂，对文件进行分析（parse）也很轻松。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AOF 持久化方式的缺点：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;对于相同的数据集，AOF 文件的体积通常要大于 RDB 文件的体积；&lt;/li&gt;
  &lt;li&gt;根据所使用的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 策略，AOF 的速度可能会慢于 RDB。在一般情况下，每秒 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 的性能依然非常高，而关闭 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 可以让 AOF 的速度和 RDB 一样快，即使在高负荷之下也是如此。不过在处理巨大的写入载入时，RDB 可以提供更有保证的最大延迟时间；&lt;/li&gt;
  &lt;li&gt;AOF 在过去曾经发生过这样的 bug ：因为个别命令的原因，导致 AOF 文件在重新载入时，无法将数据集恢复成保存时的原样。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;使用-aof&quot;&gt;使用 AOF&lt;/h3&gt;
&lt;p&gt;开启 AOF 功能需要设置配置：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appendonly yes&lt;/code&gt;，默认不开启。AOF 文件名通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appendfilename&lt;/code&gt; 设置，默认文件名是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appendonly.aof&lt;/code&gt;，保存路径同 RDB 持久化方式一致，通过 dir 配置指定。AOF 工作流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;命令写入 (append): 所有的写入命令会追加到 aof_buf (缓冲区) 中, AOF 命令写入的内容直接是文本协议格式。&lt;/li&gt;
  &lt;li&gt;文件同步 (sync): AOF 缓冲区根据对应的策略向硬盘做同步操作;&lt;/li&gt;
  &lt;li&gt;文件重写 (rewrite): 随着 AOF 文件越来越大，需要定期对 AOF 文件进行重写，达到压缩的目的;&lt;/li&gt;
  &lt;li&gt;重启加载 (load): 当 Redis 服务器重启时，可以加载 AOF 文件进行数据恢复。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;文件同步&quot;&gt;文件同步：&lt;/h3&gt;
&lt;p&gt;Redis 提供了三种 AOF 缓冲区同步文件策略，由参数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appendfsync&lt;/code&gt; 控制，可配置值为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;always&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;everysec&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;配置为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;always&lt;/code&gt; 时，命令写入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aof_buf&lt;/code&gt; 后调用系统 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 操作同步到 AOF 文件，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 完成后线程返回。每次写入都要同步 AOF 文件，在一般的 SATA 硬盘上，Redis 只能支持大约几百 TPS 写入，显然跟 Redis 高性能特性背道而驰，不建议配置。&lt;/li&gt;
  &lt;li&gt;配置为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no&lt;/code&gt; 时，命令写入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aof_buf&lt;/code&gt; 后调用系统 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; 操作, 不对 AOF 文件做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 同步，同步硬盘操作由操作系统负责。由于操作系统每次同步 AOF 文件的周期不可控，而且会加大每次同步硬盘的数据量，虽然提升了性能，但数据安全性无法保证。&lt;/li&gt;
  &lt;li&gt;配置为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;everysec&lt;/code&gt;，命令写入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aof_buf&lt;/code&gt; 后调用系统 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; 操作, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; 完成后线程返回，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fsync&lt;/code&gt; 同步文件操作由专门线程每秒调用一次。是建议的同步策略，也是默认配置，做到兼顾性能和数据安全性，理论上只有在系统突然宕机的情况下丢失 1 秒的数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;重写机制&quot;&gt;重写机制：&lt;/h3&gt;
&lt;p&gt;随着命令不断写入 AOF，文件会越来越大，为了解决这个问题，Redis 引入 AOF 重写机制压缩文件体积。AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程。重写后的 AOF 文件变小有如下原因：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;进程内已经超时的数据不再写入文件。&lt;/li&gt;
  &lt;li&gt;旧的 AOF 文件含有无效命令，重写使用进程内数据直接生成，这样新的 AOF 文件只保留最终数据的写入命令。&lt;/li&gt;
  &lt;li&gt;多条写命令可以合并为一个，为了防止单条命令过大造成客户端缓冲区溢出，对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zset&lt;/code&gt; 等类型操作，以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;64&lt;/code&gt; 个元素为界拆分为多条。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AOF 重写过程可以手动触发和自动触发：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;手动触发：直接调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgrewriteaof&lt;/code&gt; 命令。&lt;/li&gt;
  &lt;li&gt;自动触发：根据 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto-aof-rewrite-min-size&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto-aof-rewrite-percentage&lt;/code&gt; 参数确定自动触发时机。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto-aof-rewrite-min-size&lt;/code&gt; 表示运行 AOF 重写时文件最小体积，默认为 64MB。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto-aof-rewrite-percentage&lt;/code&gt; 代表当前 AOF 文件空间（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aof_current_size&lt;/code&gt;）和上一次重写后 AOF 文件空间（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aof_base_size&lt;/code&gt;）的比值。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;自动触发时机 = aof_current_size &amp;gt; auto-aof-rewrite-min-size &amp;amp;&amp;amp;（aof_current_size-aof_base_size）/ aof_base_size &amp;gt;= auto-aof-rewrite-percentage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AOF 重写流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;执行 AOF 重写请求。如果当前进程正在执行 AOF 重写，请求不执行；如果当前进程正在执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 操作，重写命令延迟到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 完成之后再执行；&lt;/li&gt;
  &lt;li&gt;父进程执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 创建子进程，开销等同于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgsave&lt;/code&gt; 过程；&lt;/li&gt;
  &lt;li&gt;主进程 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作完成后，继续响应其他命令。所有修改命令依然写入 AOF 缓冲区并根据 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appendfsync&lt;/code&gt; 策略同步到硬盘，保证原有 AOF 机制正确性；&lt;/li&gt;
  &lt;li&gt;由于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作运用写时复制技术，子进程只能共享 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; 操作时的内存数据。由于父进程依然响应命令，Redis 使用“AOF 重写缓冲区”保存这部分新数据，防止新 AOF 文件生成期间丢失这部分数据&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;重启加载&quot;&gt;重启加载:&lt;/h3&gt;
&lt;p&gt;AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。Redis 持久化文件加载流程如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;AOF 持久化开启且存在 AOF 文件时，优先加载 AOF 文件；&lt;/li&gt;
  &lt;li&gt;AOF 关闭或者 AOF 文件不存在时，加载 RDB 文件；&lt;/li&gt;
  &lt;li&gt;加载 AOF/RDB 文件成功后，Redis 启动成功；&lt;/li&gt;
  &lt;li&gt;AOF/RDB 文件存在错误时，Redis 启动失败并打印错误信息。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;rdb-和-aof-的选择&quot;&gt;RDB 和 AOF 的选择&lt;/h2&gt;
&lt;p&gt;通常情况下，如果非常关注数据安全性，应该同时使用两种持久化功能。如果可以承受数分钟以内的数据丢失，那么可以只使用 RDB 持久化。并不推荐单独使用 AOF 方式，因为定时生成 RDB 快照（snapshot）非常便于进行数据库备份，并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快，除此之外，使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。&lt;/p&gt;

&lt;h2 id=&quot;参考资料&quot;&gt;参考资料&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://redis.io/topics/persistence&quot;&gt;Redis Persistence&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 08 Apr 2018 00:00:00 +0800</pubDate>
        <link>https://www.yuxiumin.com/2018/04/08/redis-learning-notes-persistence/</link>
        <guid isPermaLink="true">https://www.yuxiumin.com/2018/04/08/redis-learning-notes-persistence/</guid>
        
        <category>Redis</category>
        
        <category>缓存</category>
        
        
      </item>
    
  </channel>
</rss>
