<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Stein&#39;s World</title>
    <link>https://steinliber.github.io/index.xml</link>
    <description>Recent content on Stein&#39;s World</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 24 Feb 2017 13:50:46 +0200</lastBuildDate>
    <atom:link href="https://steinliber.github.io/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>在线进行大规模的数据迁移[译]</title>
      <link>https://steinliber.github.io/post/online-migrations/</link>
      <pubDate>Fri, 24 Feb 2017 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/online-migrations/</guid>
      <description>

&lt;p&gt;工程师团队在构建软件时会面临一个普遍的挑战：为了支持整洁的抽象和愈加复杂的特性，他们通常需要重新设计所使用的数据模型。在生产环境中，这或许就意味着要迁移百万级的活跃对象和重构数千行的代码。&lt;/p&gt;

&lt;p&gt;Stripe 的用户期望我们的接口是可用并且一致的。这就意味着当我们在做迁移的时候需要格外的小心：我们需要明确储存在系统中每一个对象的含义及值，同时也需要确保 Stripe 在任何时候都能为用户提供服务。&lt;/p&gt;

&lt;p&gt;在这篇文章中，我们将会说明我们是如何对数以百万的订阅对象进行安全的大规模迁移。&lt;/p&gt;

&lt;h2 id=&#34;为什么迁移是困难的&#34;&gt;为什么迁移是困难的?&lt;/h2&gt;

&lt;h3 id=&#34;规模&#34;&gt;规模&lt;/h3&gt;

&lt;p&gt;Stripe 有数亿的订阅对象。运行一次涉及所有这些对象的大规模迁移对于我们的生产数据库来说意味着大量的工作。&lt;/p&gt;

&lt;p&gt;假设每个对象的迁移都要耗费 1 秒钟：以这个线性增长的方式计算，迁移数亿的对象要花掉超过三年的时间。&lt;/p&gt;

&lt;h3 id=&#34;上线时间&#34;&gt;上线时间&lt;/h3&gt;

&lt;p&gt;商家在 Stripe 上持续不断的进行交易。我们在线上进行所有的基础设施升级，而不是依赖于计划中的维护期。因为我们在迁移过程中不能只是简单的暂停这些订阅，我们必须保证所有交易的执行都可以在我们的所有服务器上 100% 运行。&lt;/p&gt;

&lt;h3 id=&#34;精确性&#34;&gt;精确性&lt;/h3&gt;

&lt;p&gt;我们的订阅表在代码库的许多不同地方都会用到。如果我们想一次性在订阅服务中修改上千行的代码，我们几乎可以确信会忽略掉一些边界条件。我们需要确保每一个服务可以继续依赖于精确的数据。&lt;/p&gt;

&lt;h2 id=&#34;在线迁移的一个模式&#34;&gt;在线迁移的一个模式&lt;/h2&gt;

&lt;p&gt;从一个表迁移百万级数据到另一个表是一件极为困难但是是许多公司不得不面对的一件事。&lt;/p&gt;

&lt;p&gt;这里有一个通用的 4 步&lt;strong&gt;双重写入模式&lt;/strong&gt;，人们经常使用像这样的模式来做线上的大规模迁移。这里是它如何工作的&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;li&gt;&lt;strong&gt;移除依赖于过期数据模型的旧数据&lt;/strong&gt; 。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;我们迁移的例子-订阅&#34;&gt;我们迁移的例子: 订阅&lt;/h2&gt;

&lt;p&gt;什么是订阅以及我们为什么需要做迁移？&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://stripe.com/subscriptions&#34;&gt;Stripe 订阅&lt;/a&gt; 帮助像 &lt;a href=&#34;https://www.digitalocean.com/&#34;&gt;DigitalOcean&lt;/a&gt; 和 &lt;a href=&#34;https://www.squarespace.com/&#34;&gt;Squarespace&lt;/a&gt; 的用户建立和管理它们消费者的定期结算，在这过去的几年中，我们已经添加了许多特性去支持它们越来越复杂的账单模型，比如说多方订阅、试用、优惠券和发票。&lt;/p&gt;

&lt;p&gt;在早些时候，每个消费者对象最多可以有一个订阅。我们的消费者被当作独立的记录储存。因为消费者和订阅的映射是直接的，所以订阅是和消费者是一起储存的。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Customer
  Subscription subscription
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终，我们意识到有些用户想要创建有多个订阅表的消费者。我们决定把 &lt;code&gt;subscription&lt;/code&gt; 字段（只支持一个订阅）转换成&lt;code&gt;subscriptions&lt;/code&gt;字段，这样我们就可以储存一个有多个活跃订阅的数组。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Customer
  array: Subscription subscriptions
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在我们添加新特性的时候，发现这个数据模型会有问题。任何对消费者订阅的改变都意味着要更新整个消费者模型，而且和订阅相关的查询也会在消费者对象中查询。所以我们决定分开储存活跃的订阅。&lt;/p&gt;

&lt;p&gt;我们重新设计了数据模型从而把订阅移到订阅表中。&lt;/p&gt;

&lt;p&gt;提醒一下⏰，我们的 4 个迁移阶段是&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;li&gt;&lt;strong&gt;移除依赖于过期数据模型的旧数据&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;让我们像实践中一样来体验这4个阶段。&lt;/p&gt;

&lt;h2 id=&#34;part-1-双重写入&#34;&gt;Part 1: 双重写入&lt;/h2&gt;

&lt;p&gt;在开始迁移之前，首先我们会创建一个新的数据库表。第一步就是开始复制新消息，以便将其写入到两个储存中。我们之后会将缺失的数据回填到新的储存中，以便两个储存保存相同的信息。&lt;/p&gt;

&lt;p&gt;所有新的写入都应该更新到这两个储存。&lt;/p&gt;

&lt;p&gt;在我们的例子中，我们将所有新创建的订阅记录写到 Customers 和 Subscriptions 表中。在我们开始双重写入这两张表之前，这种额外的写入对我们生产数据库产生的潜在影响是值得我们考虑的。我们可以通过降低提高复制对象的百分比的速度来缓解性能问题，同时仔细检查运行时的指标。&lt;/p&gt;

&lt;p&gt;这时候，新创建的对象会同时存在于两个表中，而旧的数据只能在旧的表中找到。我们将会以惰性方式开始复制已经存在的订阅：每当对象更新，它们将自动被复制到新表中。这个方法让我们开始逐步转移现有的订阅。&lt;/p&gt;

&lt;p&gt;最终，我们会将所有剩余的消费者订阅数据回填到新的 Subscriptions 表中。&lt;/p&gt;

&lt;p&gt;我们需要将已经存在的订阅回填到新的 Subscriptions 表中。&lt;/p&gt;

&lt;p&gt;要在一个实时的数据库上回填一个新表其中最重要的是如何简单找到所有需要迁移的对象。通过查询数据库来查找所有对象会在生产数据库执行大量查询，这将需要很多时间。幸运的是，我们可以将这个过程分流成对我们生产数据库没影响的离线过程。我们为我们的 Hadoop 集群提供了我们的数据库快照，这使我们能够使用 &lt;a href=&#34;https://en.wikipedia.org/wiki/MapReduce&#34;&gt;MapReduce&lt;/a&gt; 通过离线、分布式的方式快速处理我们的数据。&lt;/p&gt;

&lt;p&gt;我们使用 &lt;a href=&#34;https://github.com/twitter/scalding&#34;&gt;Scalding&lt;/a&gt; 来管理我们的 MapReduce 任务。Scalding 是一个由 Scala 编写的有用的库，可以帮助我们方便的编写 MapReduce 的 Job (您可以用 10 行代码编写一个简单的 Job)。在这里，我们将会使用 Scalding 来帮助我们识别所有的订阅。我们将会遵循以下步骤：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;编写一个Scalding job，该 job 提供需要复制的所有订阅数据 ID 的列表。&lt;/li&gt;
&lt;li&gt;运行大型多线程迁移，通过一系列可以有效对我们的数据并行操作的进程来复制这些订阅。&lt;/li&gt;
&lt;li&gt;迁移完成后，再次运行 Scalding Job，以确保订阅表中没有遗漏的订阅。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;part-2-修改全部的写路径&#34;&gt;Part 2: 修改全部的写路径&lt;/h2&gt;

&lt;p&gt;现在新的和旧的数据都已经同步储存了，下一步就是开始使用新的数据储存来读取我们的全部数据。&lt;/p&gt;

&lt;p&gt;到现在为止，所有的读操作都使用已经存在的 Customers 表：我们需要将这些操作移到 Subscriptions 表中。&lt;/p&gt;

&lt;p&gt;我们需要确保从新的 Subscriptions 表中读取数据是安全的：我们的订阅数据需要保持一致性。我们将会使用 GitHub 的 &lt;a href=&#34;https://github.com/github/scientist&#34;&gt;Scientist&lt;/a&gt; 来帮助验证我们的数据读取路径。Scientist 是一个 Ruby 库可以让你运行试验并比较不同代码路径的运行结果，如果两个试验在生产中出现了不同的结果它就会提醒你。有了 Scientist，我们就可以在实时运行中生成不同结果的警告和指标。当一份试验代码路径上产生了一个错误，我们其余的应用并不会受此影响。&lt;/p&gt;

&lt;p&gt;我们将会运行以下试验:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 Scientist 来同时从 Subscriptions 表和 Customers 表读取数据。&lt;/li&gt;
&lt;li&gt;如果结果并不匹配，产生一个错误来提醒我们的工程师这个结果不一致。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub 的 Scientist 让我们可以运行从两张表读取数据的试验并且比较运行的结果。&lt;/p&gt;

&lt;p&gt;在我们验证所有试验都匹配上之后，我们开始从新表中读取数据。&lt;/p&gt;

&lt;p&gt;我们的试验是成功的：所有读的操作现在都是使用新的订阅表。&lt;/p&gt;

&lt;h2 id=&#34;part-3-修改所有写路径&#34;&gt;Part 3: 修改所有写路径&lt;/h2&gt;

&lt;p&gt;接下来，我们需要更新全部的写路径到我们新的 Subscriptions 储存。我们的目标是逐步推进这些变化，因此我们需要采取谨慎的策略。&lt;/p&gt;

&lt;p&gt;一直到现在 ，我们已经把数据写入到旧的储存中并且把它们复制到新的储存中：&lt;/p&gt;

&lt;p&gt;我们现在要逆转顺序：把数据写入到新的储存中之后再把它归档到旧的储存中。通过保持着两个储存的数据相互一致性，我们可以逐步更新并且仔细观察每一步的改变。&lt;/p&gt;

&lt;p&gt;在我们改变订阅的过程中最具有挑战性的部分就是重构所有的代码路径。Stripe 处理订阅操作(例如更新、划分、续订）的逻辑跨多个服务和数千行代码。&lt;/p&gt;

&lt;p&gt;能够成功重构的关键就是我们的逐步过程：我们将隔离尽可能多的代码路径到最小的单元，从而可以仔细实现每个更改。我们的两张表需要在每一步都保持一致。&lt;/p&gt;

&lt;p&gt;对于每一个代码路径，我们都需要使用一个整体的方法来确保我们所做的改变是安全的。我们不能只是用新纪录来替换老纪录：每一段的逻辑都需要仔细考虑。如果我们忽略任何情况，或许就会出现数据不一致的情况。幸运的是，我们可以运行更多的 Scientist 试验来提醒我们这个过程中任何潜在的数据不一致。&lt;/p&gt;

&lt;p&gt;我们新的简化写入路径如下所示：&lt;/p&gt;

&lt;p&gt;我们可以确保没有代码块继续使用过时的 &lt;code&gt;subscriptions&lt;/code&gt; 数组，任何对这个数组的调用都会引发错误：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Customer
  def subscriptions
    hard_assertion_failed(&amp;quot;Accessing subscriptions array on customer&amp;quot;)
  endend
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;part-4-移除旧数据&#34;&gt;Part 4: 移除旧数据&lt;/h2&gt;

&lt;p&gt;我们最终（也是最惬意）的一步是删除写入旧储存的代码并最后删除旧储存。&lt;/p&gt;

&lt;p&gt;一旦我们确信再没有代码依赖于已经过期的数据模型的 &lt;code&gt;subscriptions&lt;/code&gt; 字段，我们就再也不需要把数据写入到旧的表里了。&lt;/p&gt;

&lt;p&gt;有了这些改变，我们的代码再也不使用旧的储存，而新的表成为了我们可靠·的数据来源。&lt;/p&gt;

&lt;p&gt;我们现在可以删除所有 Customer 对象的 &lt;code&gt;subscriptions&lt;/code&gt; 数组，我们将会以惰性的方式来逐步进行删除。我们首先在每次加载订阅对象时自动清空数组，之后再运行一个最后的 Scalding job 和迁移来找到所有剩余要删除的对象。最终我们得到了所需要的模型。&lt;/p&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;在保持 Stripe API 的一致性的同时运行迁移是复杂的。这里有帮助我们安全运行迁移的一些点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我们制定了一份包含 4 个阶段的迁移策略，这个策略允许我们保持生产服务器运行的同时完成数据储存的转移，而没有任何下线时间。&lt;/li&gt;
&lt;li&gt;我们使用 Hadoop 离线处理数据，这让我们可以使用 MapReduce 以并行的方式来管理高数据量，而不是依赖于生产数据库上昂贵的查询。&lt;/li&gt;
&lt;li&gt;所有做的改变都是逐步的。我们从来都没有企图一次改超过几百行的代码。&lt;/li&gt;
&lt;li&gt;我们所做的所有改变都是高度透明和可观察的。一旦任何数据出现了不一致，Scientist 试验都会提醒我们。在每一步，我们都对我们的安全迁移抱有信心。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们发现这种方法在 Stripe 上执行的许多在线迁移中都非常有效。我们希望这些实践对其他团队执行大规模迁移会有用。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>一个django APP 的服务器部署</title>
      <link>https://steinliber.github.io/post/django_app_deploy/</link>
      <pubDate>Thu, 23 Feb 2017 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/django_app_deploy/</guid>
      <description>

&lt;p&gt;相信很多后端开发者应该经历过代码在本机上跑的好好的，但一部署到服务器上就出差错了，那其中到底发生了什么?下面我就来扒一扒我在部署一个django应用过程中的心得体会。&lt;/p&gt;

&lt;h3 id=&#34;代码&#34;&gt;代码&lt;/h3&gt;

&lt;p&gt;要运行一个app最重要的就是代码，我们需要先把代码放到服务器上。把代码传到部署服务器上有很多办法，目前主流的是在部署服务器上使用git来拉取代码，这样就能在服务器上进行代码的版本控制。当然也可以用scp拷贝到服务器上（不推荐）&lt;/p&gt;

&lt;h2 id=&#34;依赖&#34;&gt;依赖&lt;/h2&gt;

&lt;p&gt;一个app要满足所有的依赖关系以后，才能够正常运行，对于django应用至少django包是必不可少的。在python中可以通过 requirements.txt 来安装这些依赖。一般来说要为每个应用创建一个单独的虚拟环境，这样可以防止一个服务器上多个app之间依赖的互相影响。&lt;/p&gt;

&lt;h2 id=&#34;环境&#34;&gt;环境&lt;/h2&gt;

&lt;p&gt;这里的环境指的是app的运行配置，包括数据库链接的地址，SECRET KEY这些配置，本地环境下的环境配置和线上的一般是不一样的。在12-factor中推荐用环境变量来实现环境的相关配置，django可以使用django-environ来实现。到这一步为止，线上服务器的这个app应该可以直接在3服务器上运行了也就是自启动应用。&lt;/p&gt;

&lt;h2 id=&#34;服务&#34;&gt;服务&lt;/h2&gt;

&lt;p&gt;上面的应用是已经可以启动了，但如果你连的数据库是本地，那运行中肯定是会报错的，这些app运行过程中所依赖的服务我把它称为依赖服务，还有像logstash日志收集服务，uwsgi服务器服务，supervisor进程管理服务等，这些对于一个app的运行来说并非必要的服务我把它称为附加服务。对于一个django应用，可以用uwsgi来替代django内置的服务器，用nginx来做反向代理和对外的服务器，用logstash来收集运行过程中的日志，用supervisor来管理其它的进程。
##综述
其实对于一个线上的应用来说，可以这样说 app = 代码+依赖+环境， 而一个server= app +服务，这样来看我们就可以把一个 django 应用的部署抽象出来，使用像 saltstack 的工具来部署大多数的 django 应用，最近在写一个 Django 应用的自动部署平台，下次可以分享一下。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Python 中的协程</title>
      <link>https://steinliber.github.io/post/python_async/</link>
      <pubDate>Mon, 23 Jan 2017 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/python_async/</guid>
      <description>&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;并发描述的是一个程序是否被分成了多个可执行片段，使得每个片段都可以独立运行,不同执行片段通过通信来进行协调。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;并发与并行
首先应该明确的一点就是并发和并行的概念.并发是程序级的概念，描述的是一段程序里面可执行片段的多少，所以在程序中使用进程，线程，协程都能提高程序的并发性。而并行描述的是程序的计算过程，在同一时间内，该程序同时执行多个可执行片段。现代操作系统中的最小可执行单位是线程，你创建一个进程，操作系统实际执行单位也是该进程中的线程，也就是说只要你的计算机CPU是有多核的，而且你的程序被分成了多个线程或者进程，那每个CPU都会执行其中的一个线程 （没别的任务的情况下）这时候程序就是并行的。而协程只是一个线程中的一部分执行片段，所以单纯的使用协程并不能提高程序的并行性，只能提高单核CPU的并发性。下图表明在双核CPU的计算机下多线程和多协程的运行情况（python中因为GIL的存在是无法做到多线程的，图中只是理想情况下-。-）。
&lt;img src=&#34;http://upload-images.jianshu.io/upload_images/1682167-b082f5565ca29ce4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&#34; alt=&#34;多线程多协程代码的执行情况&#34; /&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;协程的应用场景
要了解 协程，首先要知道当 cpu 处理系统 io 操作时，会等待这些操作的完成，你可知道这短短的等待时间 cpu 可以执行多少命令，下面是 node.js 的创始人在一次演讲上展示的图。
&lt;img src=&#34;http://upload-images.jianshu.io/upload_images/1682167-1473da0167ff5264.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&#34; alt=&#34;现在的电脑从不同的储存设备中读取数据的延迟(最后一列只是类比)&#34; /&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;当今的计算机 cpu 普遍性能可以达到 2000000000 次/秒，简单计算下从硬盘中取数据会让 cpu 什么都不干 0.0025 秒，那段时间本来可以执行 5000000 条指令啊 -_-。于是就诞生了两种方法来防止io的阻塞。
   (1) 为每个会阻塞的操作单独创建一个线程，这样你取你的，我运行我的，就可以提高cpu的利用率
   (2)把每个会阻塞的操作变成一个异步调用，我先跑我的，等你取完了通知我再收拾你
   线程的切换有个问题，切换线程消耗的资源多，还有锁。其实最重要的是python因为gil的存在，对线程的支持十分有限。
   在第二种方法中可以分为异步回调和协程两种方式。这两者最大的区别是协程在内存中维护自身的运行状态和上下文，这样程序的逻辑流表现起来就非常简单，而异步回调则是通过在调用函数中注册回调函数，调用函数的运行状态通过回调函数返回。所以一般来说回调函数的实现都会比协程复杂（关于回调具体可以看&lt;a href=&#34;http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/）&#34;&gt;http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/）&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;python中的协程
在python中协程的实现是通过yield来实现的，有些同学可能知道yield是用来把函数变成生成器的却不知道它也可以用于协程。其实仔细一想你就知道，yield命令其实会保存当前函数的上下文，然后返回一个数值给调用它的函数。再想想上面的协程实现，嘿嘿，懂了吧。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;总的来说，协程作为一个程序级的实现，优化了程序 io 性能，同时把异步回调的实现变成了同步，再加上python中 asyncio 库的支持，非常推荐大家去学习下。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>elasticsearch 使用中的坑</title>
      <link>https://steinliber.github.io/post/elasticsearch_fill/</link>
      <pubDate>Tue, 17 Jan 2017 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/elasticsearch_fill/</guid>
      <description>&lt;p&gt;在Es的使用过程中，会遇到许多的坑，在这里总结下我所遇到的一些问题和解决方法。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Es重建索引
Es的索引不像mysql等数据库那样可以方便的更改和删除字段，所以如果ES中涉及到字段的变更，就需要重建索引。在Es中存在大量数据的情况下怎样才能做到ES保持正常服务的同时又能建立新的索引？
（1）首先我们要创建一个alias指向当前索引，所有对当前索引的操作都通过alias（相当于软连接）
（2）然后我们需要创建一个名字不同的索引（最好带上版本号）并创建自己所需要的映射关系
（3）使用Es提供的reindex将现有索引的数据全部导入到新的索引中
（4）将（1）中的假名指向新索引，这样所有的请求都会有新索引来处理
（5）删除旧的索引
上述方法在索引存在大量数据reindex时会对性能产生较大影响，还有的方法就是在索引中不管需要更改的字段，直接创建新的字段，在程序中控制返回的结果，这样会使程序变得比较复杂但重建索引时对Es性能影响不大。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Es近实时搜索
Es处于性能的考虑，默认会每隔一秒钟将内存中的段保存到硬盘中，这时候保存的记录才会被搜索到，这在现实中影响不是很大，但在写测试的时候如果保存了记录却搜索不到，总不能每次都等一秒钟再搜索把。这时候可以用Es的flush将数据手动刷新到硬盘，这样数据就可以搜索到了
、&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;EsRejectedExecutionException的问题
Es中处理数据时会将请求的数据在队列中保存起来，当请求的数据数量过多，es的处理速度跟不上时，es要处理的数据会超过队列的长度，这些多出来的数据就会返EsRejectedExecutionException异常。在我的Mac上（8G内存，I5处理器）在Es上建立80万的数据，有1万个数据因为EsRejectedExecutionException而丢失。处理这个问题有两种解决方法&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;提高本机的性能，对es进行相关配置
Es处理的速度如果能高于请求建立索引的速度，那自然不会有这个问题。Es中默认的相关配置比较低，我们可以进行一定的配置来提高处理性能，如可以在环境变量中设置ES_MIN_MEM=8g，ES_MAX_MEM=8g，java我也不是很懂，估计是增大es可以使用的系统内存来提高运行速度&lt;/li&gt;
&lt;li&gt;上面的方法毕竟治标不治本，在部署环境下，不同的机子可能会有不同的性能，所以需要我们该想办法把被EsRejectedExecutionException的记录重新建立索引，实现的python代码如下所示&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;   actions = self._build_elastic_actions(elas_indexs)
         success_num, err_items = bulk(
             client=self.es,
             actions=(#要在es中保存的数据，可迭代的都行),
             chunk_size=200,
             raise_on_error=False,
             raise_on_exception=False
         )
         # 处理es拒绝服务的情况
         reject_es_data_id = [error[&#39;index&#39;][&#39;_id&#39;] for error in err_items
                              if error[&#39;index&#39;][&#39;status&#39;] == 429]
         try_time = 0
         while len(reject_es_data_id) &amp;gt; 0 and try_time &amp;lt; 5:
             time.sleep(5)
             records = Record.objects.filter(id__in=reject_es_data_id)
             re_success_num, re_error_items = bulk(
                 client=self.es,
                 actions=(#要重建的索引),
                 chunk_size=200,
                 raise_on_error=False,
                 raise_on_exception=False
             )
             reject_es_data_id = [error[&#39;index&#39;][&#39;_id&#39;] for error in err_items
                                  if error[&#39;index&#39;][&#39;status&#39;] == 429]
             success_num += re_success_num
             try_time += 1
         error_items = [error for error in err_items if error[&#39;index&#39;][&#39;status&#39;] != 429]
         error_items.extend(re_error_items)
         return success_num, error_items
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;基本思想就是每次bulk创建es的索引，检测返回的错误中有没有因为EsRejectedExecutionException异常而索引失败的，如果有就进入一个循环，在循环中休息5秒再创建被拒绝的索引，这样循环5次，还是没创建的话就退出创建剩下的。在这里我在es中创建和本地数据库id一样的记录，所以可以通过id来重新建索引。基本上就是这个意思，剩下什么错误的记录什么的实现起来都很简单。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Es的默认模版
如果你是动态创建索引的话（也就是说不创建字段的映射关系，由Es自动判断字段的类型）对于有些字段，你可能就想要这个类型，而Es却会把他创建为另一个类型。比如说你有个字段keywords，这个字段当然不能被分词，而es一般会将字符串都保存为分词的形式，这就不符合我们的需求了。
还好Es中提供了默认模版的机制，可以指定特定索引哪些字段储存为什么类型&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Es的搜索结果数量
在默认情况下，Es只会返回搜索结果的前10项，可以通过分页来获取后面的内容，但如果分页太深或者返回的结果太多，可能会导致性能问题。因为当我们请求结果的第一页（结果1到10）时，每个分片产生自己最顶端10个结果然后返回它们给请求节点，它再排序这所有的50个结果以选出顶端的10个结果。而如果我们请求第1000页时，得到的结果是10001到10010的数据。工作方式都相同，不同的是每个分片都必须产生顶端的10010个结果。然后请求节点排序这50050个结果并丢弃了剩余的50040个。当真有这种需求时，可以使用Es的scroll来进行搜索，它会在Es中记录上次搜寻的位置，我们就可以持续的从Es中拉数据。也可以用scan来禁用排序，只要有分片中有结果就返回。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Webhook 该做和不该做的: 我们在整合超过 100 个 API 中所学到的[译]</title>
      <link>https://steinliber.github.io/post/webhooks-dos-and-dont-s-what-we-learned-after-integrating-100-apis/</link>
      <pubDate>Fri, 30 Dec 2016 20:32:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/webhooks-dos-and-dont-s-what-we-learned-after-integrating-100-apis/</guid>
      <description>

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;原文地址：&lt;a href=&#34;https://restful.io/webhooks-dos-and-dont-s-what-we-learned-after-integrating-100-apis-d567405a3671&#34;&gt;Webhooks do’s and dont’s: what we learned after integrating +100 APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;原文作者：&lt;a href=&#34;Giuliano Iacobelli&#34;&gt;Giuliano Iacobelli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;译文出自：&lt;a href=&#34;https://github.com/xitu/gold-miner&#34;&gt;掘金翻译计划&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;译者：&lt;a href=&#34;https://github.com/steinliber&#34;&gt;steinliber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;校对者：&lt;a href=&#34;https://github.com/xekri&#34;&gt;xekri&lt;/a&gt; , &lt;a href=&#34;https://github.com/DeadLion&#34;&gt;DeadLion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;当现在的应用变的越来越像 API 的集合而且无服务架构获得越来越多的关注时，作为一个 API 的提供者，不应该再只是暴露传统的 REST 接口。&lt;/p&gt;

&lt;p&gt;传统 REST API 的设计是用来让你可以程序化的获取或提交内容，但在你只是想如果某些信息改变，API 再通知应用程序的情况下，传统 API 还远不够好，这还远远不是最佳的实践。如果是要实现这个的话,就需要定期轮询，而且这样还会失去扩展性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*dEmrcTajSG5A4Z_JjrGqfw.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Picture credits &lt;a href=&#34;https://medium.com/u/e6dd3fdb7c2d&#34;&gt;Lorna Mitchell&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;为了获取一小段信息，轮询 API 通常是一种既浪费又复杂的方式。一些事件或许在一段时间内只发生一次，所以你必须推断出轮询的频率。然而即使这样你也可能错过它。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t call us, we’ll call you!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;好的，webhook 就是这个问题的答案.  &lt;strong&gt;webhook 就是 Web 服务使用 HTTP POST 请求为其他服务提供近实时信息的一种方式。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*8t-MNjY-6rJ79rsDnZt0rA.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Picture credits &lt;a href=&#34;https://medium.com/u/e6dd3fdb7c2d&#34;&gt;Lorna Mitchell&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一个 webhook 会在它调用时就传递数据到其它的应用，这表明你可以立即得到数据。这让使用了 webhook 的生产者和消费者都变的更有效率，如果你的 API 还不支持这些，你真应该做一些关于这方面的事。
(关注 &lt;a href=&#34;https://medium.com/u/f4fb2a348280&#34;&gt;Salesforce&lt;/a&gt; 了吗?).&lt;/p&gt;

&lt;p&gt;当涉及到 webhooks 的设计，现在并没有类似标准的 HTTP API 这样的规范。每个服务实现不同的 webhook， 从而导致许多不同的 webhooks 实现风格。&lt;/p&gt;

&lt;p&gt;我们在集成了来自 100 多个不同服务 API 后，可以说对外提供 webhook 的服务是个大“杀器”。当我们需要集成一个暴露 webhook 的服务时，这里有些建议能够帮助到我们。&lt;/p&gt;

&lt;h4 id=&#34;自我解释和一致性&#34;&gt;自我解释和一致性&lt;/h4&gt;

&lt;p&gt;一个好的 webhook 服务应该要尽可能多的提供被通知事件的信息，以及客户端执行该事件的其他信息。&lt;/p&gt;

&lt;p&gt;客户端在创建 POST 请求的时候应该包含一个 &lt;code&gt;timestamp&lt;/code&gt; 和 &lt;code&gt;webhook_id&lt;/code&gt; 字段。如果你提供的是不同类型的 webhooks，不管它们是否被发送到单个端点都应该包含一个 &lt;code&gt;type&lt;/code&gt; 属性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/600/1*Yi85OX2kNJw-bbn8O0VVQQ.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Github webhook 携带数据的示例&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://medium.com/u/d18563e4f2b9&#34;&gt;GitHub&lt;/a&gt; 非常完美的实现了以上这点。 请不要像 Instagram 或 Eventbrite 那样，只发送一个 ID 然后使用另一个 API 来解析。&lt;/p&gt;

&lt;p&gt;如果你认为你的在一次请求中发送的有效数据太多，请给我机会让它变的更轻量。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://medium.com/u/3ecae35d6d66&#34;&gt;Stripe&lt;/a&gt;’s &lt;a href=&#34;https://stripe.com/docs/api&#34;&gt;event types&lt;/a&gt; 就是一个很好的例子。&lt;/p&gt;

&lt;h4 id=&#34;允许消费者定义多个-urls&#34;&gt;允许消费者定义多个 URLs&lt;/h4&gt;

&lt;p&gt;当你构建你的 webhooks ，你应该考虑到在另一端的人必须去接收你的数据。如果只给予他们在一个网址下订阅活动的机会，那肯定不是你所可以提供的最好的开发者体验。如果我需要在不同的系统上监听相同的事件就会遇到麻烦，然后我就需要把类似 Reflector.io 的库来在系统间管理数据。&lt;a href=&#34;https://medium.com/u/ce5450a7b906&#34;&gt;Clearbit&lt;/a&gt; 请开发这样的好的 API, 并相应加快你的 webhook 开发进程。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://medium.com/u/7ca8972daf76&#34;&gt;Intercom&lt;/a&gt; 在这方面做的非常好，让你可以添加多个 URLs，并为其中的每一个都定义想监听的事件。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*lGfFqT7G4x3swfm1qkxjfA.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Intercom 的 webhook 管理面板&lt;/p&gt;

&lt;h4 id=&#34;基于-ui-的订阅与基于-api-的订阅&#34;&gt;基于 UI 的订阅与基于 API 的订阅&lt;/h4&gt;

&lt;p&gt;一旦整合完成，我们应该如何处理实际订阅的创建？一些服务选择了使用 UI 来引导你完成订阅的设置，其他服务则为此提供了 API。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/600/1*lQ5VTo4IF50IjaimPq-F4Q.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://medium.com/u/26d90a99f605&#34;&gt;Slack&lt;/a&gt; 两种都支持。&lt;/p&gt;

&lt;p&gt;它提供了一个精巧的UI，这使创建订阅很容易，并且它也提供了一个稳定的事件 API（仍然没有提供尽可能多的事件，比如说他们的实时消息传递 API ，但我相信他们的工作）&lt;/p&gt;

&lt;p&gt;在选择是否为 Webhooks 提供 API 时，需要记住的一件重要的事情是，订阅将以什么规模和粒度提供，以及谁将会配置它们。&lt;/p&gt;

&lt;p&gt;我发现让人感到好奇的是像 &lt;a href=&#34;https://medium.com/u/772bf2413f17&#34;&gt;MailChimp&lt;/a&gt; 这样的工具会迫使非技术的群体混淆 webhooks 配置。这些工具通过 API 提供 webhooks ，任何具有 Mailchimp 集成的第三方服务（例如 Stamplay，Zapier 或 IFTTT）都可以通过程序化的方式来实现，从而提供更好的用户体验。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/600/1*EEMaCdPa63smJ3oOSpQ60w.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;要通过 API 创建新的 webhooks 订阅，你就应该像 HTTP API 中的任何其他资源一样来处理 &lt;strong&gt;订阅&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;最近我们在工作中发现非常好的例子是由 Box 团队在今年&lt;a href=&#34;https://blog.box.com/blog/box-webhooks/&#34;&gt;夏天&lt;/a&gt;更新的 webhook 实现。&lt;/p&gt;

&lt;h4 id=&#34;webhooks-安全&#34;&gt;webhooks 安全&lt;/h4&gt;

&lt;p&gt;一旦有人配置他的服务从你的 webhook 接收有效信息，它将会监听任何发送到端点的有效信息。&lt;/p&gt;

&lt;p&gt;如果消费者的应用程序会暴露敏感信息，那么它可以（可选）验证请求是否由你的服务生成的，而不是第三方假装是你。这种验证不是必需的，但为消息传输提供了一个额外的验证层。&lt;/p&gt;

&lt;p&gt;现在有很多方法可以实现安全性，如果你想把安全性处理放在消费者一方，你可以选择给他一个白名单来接受指定IP地址的请求，但更容易的方法是设置一个秘密令牌并验证相关信息。&lt;/p&gt;

&lt;p&gt;这方面可以从不同程度的复杂性开始做，比如说就像 Slack 或 Facebook 做的那样，在一开始使用一个纯文本共享的秘钥。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://cdn-images-1.medium.com/max/800/1*qyzDKFf4CfPwJEozGIah0w.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;至于更复杂的实现。比如说 Mandrill 对 webhook 请求进行签名，webhook POST 请求的 HTTP 头部包含了附加的&lt;code&gt;X-Mandrill-Signature&lt;/code&gt; ，这个头中将包含请求的签名。要验证 Webhook 请求，就要使用 Mandrill 相同的密钥生成签名，并将这个签名与 &lt;code&gt;X-Mandrill-Signature&lt;/code&gt; 头里的值进行比较。&lt;/p&gt;

&lt;h4 id=&#34;具有过期日期的订阅&#34;&gt;具有过期日期的订阅&lt;/h4&gt;

&lt;p&gt;现在对外提供整合了过期时间的订阅服务可能性不是很高，但可以我们可以看到这可以作为一个更常见的功能。 Microsoft Graph API 就是一个例子。除非你进行续订，否则通过 API 执行的任何订阅都将在 72 小时后过期。&lt;/p&gt;

&lt;p&gt;从数据提供商的角度来看，这是有道理的。你不想继续向可能不再运行或对你数据感兴趣的服务发送 POST 请求，但对所有真正对此感兴趣的用户来说，这是一个令人不快的体验。你是微软：如果你做不了应该做的繁重工作那又应该谁去做呢？&lt;/p&gt;

&lt;h4 id=&#34;总结&#34;&gt;总结&lt;/h4&gt;

&lt;p&gt;webhook 领域的设计仍然是分散的，但是常见的模式终究会显露出来。&lt;/p&gt;

&lt;p&gt;在 &lt;a href=&#34;https://stamplay.com/&#34;&gt;&lt;strong&gt;Stamplay&lt;/strong&gt;&lt;/a&gt; API 集成是一个问题。我们每天都面临着集成的挑战，像 Swagger ，RAML 或 API Blueprint 这样的 OpenAPI 规范并不能有所帮助，因为它们都不支持webhook 场景。&lt;/p&gt;

&lt;p&gt;所以如果你正在考虑实现 webhooks ，我邀请你想想他们的使用说明，看看例子
&lt;a href=&#34;https://medium.com/u/d18563e4f2b9&#34;&gt;GitHub&lt;/a&gt;, &lt;a href=&#34;https://medium.com/u/3ecae35d6d66&#34;&gt;Stripe&lt;/a&gt;, &lt;a href=&#34;https://medium.com/u/7ca8972daf76&#34;&gt;Intercom&lt;/a&gt; 和 &lt;a href=&#34;https://medium.com/u/272cd95a3742&#34;&gt;Slack API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PS. &lt;a href=&#34;https://medium.com/u/504c7870fdb6&#34;&gt;Medium&lt;/a&gt; 任何关于 webhooks 的想法？拜托，RSS 是老一套的做法啦。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;更新&lt;/strong&gt;: Medium 实际上提供了一种通过 &lt;a href=&#34;http://medium.superfeedr.com/&#34;&gt;http://medium.superfeedr.com/&lt;/a&gt; 实时通知的方式👌&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elasticsearch 简单介绍</title>
      <link>https://steinliber.github.io/post/elasticsearch_intorduce/</link>
      <pubDate>Fri, 30 Dec 2016 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/elasticsearch_intorduce/</guid>
      <description>

&lt;p&gt;elasticsearch是一个分布式的全文检索引擎，所谓分布式指的是elasticsearch可以很方便的水平扩容，当你觉得一个elasticsearch节点满足不了你的需求时，可以直接将新的elasticsearch节点加入这个集群来提供更高效的服务。而全文检索指的就你查找一个词，所有与这个词相关的文章都会按照相关性顺序返回。&lt;/p&gt;

&lt;h2 id=&#34;elasticsearch-全文检索的实现原理&#34;&gt;elasticsearch 全文检索的实现原理&lt;/h2&gt;

&lt;p&gt;传统的数据库如果想找到数据里面包含特定值的那些记录，基本上就要遍历所有相关的记录进行比较，当数据量不是很大时，对性能影响不是很大，但当数据量达到千万级甚至亿万级时，这样遍历不仅耗时间，而且会消耗大量系统资源。而且这样也找不到含近义词的数据，更别说按相关度排序了。那elasticsearch是如何解决这个问题的呢?
没错，就是倒排索引，既然从文章中找词效果不是很理想，那我们可以倒着来&amp;ndash;&amp;gt;根据词来找到文章，这样一切就迎刃而解了。当我们把一篇文章保存在elasticsearch中时，它会先把这篇文章分解成一个个的词*（通过分词器，用户可自行选择），然后创建并维护这些词到文章的映射关系，当在elasticsearch中进行查询时也是一样，它会把查询的词分成每个独立的词，在维护的映射关系中查找得到对应的文章返回。
比如说我现在在elasticsearch中保存了下面的一句话，该纪录的id是123（好吧，我是有点小文艺的;）
&amp;gt; 当风停住脚步时候，已经轮回了几个春秋.&lt;/p&gt;

&lt;p&gt;这句话会被拆成
&amp;gt;  风， 停住， 脚步， 脚， 步， 时候， 已经， 轮回， 轮， 回了， 回， 几个， 春秋
&amp;gt;     在elasticsearch中储存的映射关系如下（简化版，es中会有更多相关的内容，如词在文章中出现的次数也会保存，这是为了计算相关性）&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;词&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;文章在elasticsearch中保存的id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;风&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;123&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;停住&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;123&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;脚步&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;123&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;123&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这样我们就可以在elasticsearch中进行查询了，比如说我现在要查询“风和春秋”，elasticsearch就会在查询时把它拆分成
&amp;gt; 风，春秋&lt;/p&gt;

&lt;p&gt;之后在elasticsearch的映射关系中找这两个词，找到对应的文章后通过相关度算法*（和词在文章中出现的次数，文章的长度等因素有关）返回我们所想搜索的结果，这样我们就可以获取到所要搜索的记录了。当然es内部的处理不会那么简单，但对es的单个节点来说，运行的思路就是这样。
这篇文章对elasticsearch进行了简单介绍，在下一部分，我会讲讲elasticsearch中为了实现倒排索引而与传统数据库有所不同的地方。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Python 中的鸭子类型</title>
      <link>https://steinliber.github.io/post/python_duck/</link>
      <pubDate>Sat, 24 Dec 2016 13:50:46 +0200</pubDate>
      
      <guid>https://steinliber.github.io/post/python_duck/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;‘那只东西呱呱的叫，有扁扁的嘴巴，走起路来还外八，对！它就是只鸭子’&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;基本定义
对于熟悉python的开发者来说，相信对于python的鸭子类型比较熟悉，所谓鸭子类型，在维基百科中的准确定义是‘是动态类型的一种风格。在这种风格中，一个对象有效的语义，不是由继承自特定的类或实现特定的接口，而是由&amp;rdquo;当前方法和属性的集合&amp;rdquo;决定’。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;python中的具体实现
下面的代码就是一个简单的鸭子类型&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;class duck():
    def walk(self):
        print(&#39;I walk like a duck&#39;)
    def swim(self):
        print(&#39;i swim like a duck&#39;)

class person():
    def walk(self):
        print(&#39;this one walk like a duck&#39;) 
    def swim(self):
        print(&#39;this man swim like a duck&#39;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于一个鸭子类型来说，我们并不关心这个对象的类型本身或是这个类继承，而是这个类是如何被使用的。我们可以通过下面的代码来调用这些类的方法。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def watch_duck(animal):
    animal.walk()
    animal.swim()

small_duck = duck()
watch_duck(small_duck)

output &amp;gt;&amp;gt; 
I walk like a duck
i swim like a duck


duck_like_man = person()
watch_duck(duck_like_man)

output &amp;gt;&amp;gt; 
this one walk like a duck
this man swim like a duck


class Lame_Foot_Duck():
    def swim(self):
        print(&#39;i am lame but i can swim&#39;)

lame_duck = Lame_Foot_Duck()
watch_duck(lame_duck)

output &amp;gt;&amp;gt;
AttributeError: Lame_Foot_Duck instance has no attribute &#39;walk&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;watch_duck函数接收这个类的对象，然后并没有检查对象的类型，而是直接调用这个对象的走和游的方法，如果所需要的方法不存在就报错。具体在python中鸭子类型的体现如下面的代码所示&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class CollectionClass():
    lists = [1,2,3,4]
    def __getitem__(self, index):
        return self.lists[index]

iter_able_object = CollectionClass()

class Another_iterAbleClass():
    lists=[1,2,3,4]
    list_position = -1

    def __iter__(self):
        return self

    def next(self): #还有更简单的实现，使用生成器或迭代器什么的:)
        self.list_position += 1
        if self.list_position &amp;gt;3:
            raise StopIteration
        return self.lists[self.list_position]

another_iterable_object=Another_iterAbleClass()

print(iter_able_object[1])
print(iter_able_object[1:3])
output&amp;gt;&amp;gt;
2
[2, 3]

another_iterable_object[2]
output&amp;gt;&amp;gt;
Traceback (most recent call last):
  File &amp;quot;/Users/steinliber/a.py&amp;quot;, line 32, in &amp;lt;module&amp;gt;
    another_iterable_object[2]
TypeError: &#39;Another_iterAbleClass&#39; object does not support indexing

print(next(another_iterable_object))
output&amp;gt;&amp;gt;
1
print(next(another_iterable_object))
output&amp;gt;&amp;gt;
2

print(next(iter_able_object))
output&amp;gt;&amp;gt;
Traceback (most recent call last):
  File &amp;quot;/Users/steinliber/a.py&amp;quot;, line 29, in &amp;lt;module&amp;gt;
    print(next(iter_able_object))
TypeError: IterAbleClass object is not an iterator
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在python把上述代码的实现方法叫做protocol（协议），这些protocol可以看作是通知型的接口，它规定了调用方使用该功能要调用对象的哪些方法，被调用方要实现哪些方法才能完成这个功能。它和java中的接口区别在于java中的接口功能实现需要通过继承，继承的类必须实现接口中的所有的抽象方法，所以在Java中强调的是类型的概念，而python中的protocol更多的是通知性的，一个函数规定要实现某个功能需要调用传入对象的哪些方法，所有实现这些方法的类就可以实现这个功能。&lt;/p&gt;

&lt;p&gt;具体从上面两个类来说，第一个类实现了__getitem__方法,那python的解释器就会把它当做一个collection，就可以在这个类的对象上使用切片,获取子项等方法，第二个类实现了__iter__和next方法，python就会认为它是一个iterator，就可以在这个类的对象上通过循环来获取各个子项。一个类可以实现它有能力实现的方法，并只能被用于在它有意义的情况下。&lt;/p&gt;

&lt;p&gt;这两个类和上面的鸭子类相比较，其实用于切边的[](它其实调用的是python的slice函数)和用于循环的iter()就相当于watch_duck函数，这些函数都接收任意类的对象，并调用实现功能所需要的对象中的方法来实现功能，若该函数中调用的方法对象里面不存在，就报错。&lt;/p&gt;

&lt;p&gt;从上面可以看出，python鸭子类型的灵活性在于它关注的是这个所调用的对象是如何被使用的，而没有关注对象类型的本身是什么。所以在python中使用isinstance来判断传入参数的类型是不提倡的，更pythonic的方法是直接使用传入的参数，通过try，except来处理传入参数不符合要求的情况。我们应该通过传入对象的能力而不是传入对象的类型来使用该对象。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>2016 年读书回顾</title>
      <link>https://steinliber.github.io/booklist/test/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://steinliber.github.io/booklist/test/</guid>
      <description>&lt;p&gt;在过去的2016年也读了不少的书，在这里简单做个总结&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;《&lt;a href=&#34;https://book.douban.com/subject/26875239/&#34;&gt;SRE: Google运维解密&lt;/a&gt;》&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这本书讲的是 google 是怎么定义一个站点可靠性工程师的。其实现在的服务&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>