<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>quakewang</title>
    <link>https://ruby-china.org/quakewang</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>好像发现 ruby china 的一个 bug</title>
      <description>&lt;p&gt;点击一个会导致 404 的页面，比如 &lt;a href="/a-link-will-404" title=""&gt;这个链接&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后点页面上的其他链接，也无法跳转了&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Sat, 20 Mar 2021 15:54:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/41051</link>
      <guid>https://ruby-china.org/topics/41051</guid>
    </item>
    <item>
      <title>性能优化案例分析之二：时间区域查询的性能优化</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;我们在项目中经常会遇到把事件显示在日历上的需求，在项目管理，事件追踪，预约等相关功能中很常见，界面通常类似这样：
&lt;img src="https://l.ruby-china.com/photo/2017/6626e6f0-32ed-4bfb-8fe8-308b290d8a60.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;常见的做法是给对应表添加开始时间和结束时间的栏位：&lt;code&gt;start_time&lt;/code&gt; and &lt;code&gt;end_time&lt;/code&gt;，然后构建一个查询得到这个时间段内的数据，示意如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            |---给定时间段---|
|---A---| |---B---|  |-C-|   |---D---|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询要排除掉 A 和 D 这种数据，也就是结束时间小于给定的开始时间，或者开始时间大于给定的结束时间：&lt;code&gt;not (end_time &amp;lt;= :start or start_time &amp;gt;= :end)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;从 NOT (A OR B) 可以得到 (NOT A) AND (NOT B)，最终的查询语句是这样：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;start&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考虑到这是一个范围查找，通常都会给这 2 个栏位加上索引，数据量小的时候，这个查询效率很高，但在数据增加到百万级别之后，在慢查询日志里面经常会发现这个查询耗时超过几百毫秒，随着数据量的增加，甚至超过 3000 毫秒。&lt;/p&gt;
&lt;h2 id="分析过程"&gt;分析过程&lt;/h2&gt;
&lt;p&gt;继续用慢查询的分析过程套路&lt;/p&gt;
&lt;h3 id="第一步：EXPLAIN"&gt;第一步：EXPLAIN&lt;/h3&gt;
&lt;p&gt;有时候相同的查询语句，只是查询参数少许不同，会导致预估的 IO Cost 不同，执行计划也会改变，我们可以看到，这个查询有时候会用 start_time 的索引，有时候会用 end_time 的索引：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2017-05-01 00:00:00'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2017-06-01 00:00:00'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;

  &lt;span class="n"&gt;select_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;SIMPLE&lt;/span&gt;
        &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
   &lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt;
&lt;span class="n"&gt;possible_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;index_events_on_start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;index_events_on_end_time&lt;/span&gt;
          &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;index_events_on_start_time&lt;/span&gt;
      &lt;span class="n"&gt;key_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
          &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;799704&lt;/span&gt;
     &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
        &lt;span class="n"&gt;Extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;MRR&lt;/span&gt;


&lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2017-08-01 14:00:00'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2017-09-01 00:00:00'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;

  &lt;span class="n"&gt;select_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;SIMPLE&lt;/span&gt;
        &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
   &lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt;
&lt;span class="n"&gt;possible_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;index_events_on_start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;index_events_on_end_time&lt;/span&gt;
          &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;index_events_on_end_time&lt;/span&gt;
      &lt;span class="n"&gt;key_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
          &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;946430&lt;/span&gt;
     &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
        &lt;span class="n"&gt;Extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;MRR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="第二步：理解执行计划"&gt;第二步：理解执行计划&lt;/h3&gt;
&lt;p&gt;从执行计划其实已经可以看到这个慢查询的原因：需要检查的行数太多了，预估在 79 万和 94 万。但保险起见，还是从&lt;code&gt;INFORMATION_SCHEMA.OPTIMIZER_TRACE&lt;/code&gt;表中获取信息来确认我们的猜测：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "index": "index_events_on_start_time",
  "ranges": [
    "start_time &amp;lt; 0x999d02f000"
  ],
  "rows": 3147211,
  "cost": 2.73e6,
  "chosen": false
},
{
  "index": "index_events_on_end_time",
  "ranges": [
    "0x999d02e000 &amp;lt; end_time"
  ],
  "rows": 946430,
  "cost": 826083,
  "chosen": true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个查询相当于构建了 &lt;code&gt;无穷小 &amp;lt;= start_time &amp;lt;= ?&lt;/code&gt; 和 &lt;code&gt;? &amp;lt;= end_time &amp;lt;= 无穷大&lt;/code&gt; 这样 2 个开放区间查询。在这里由于前者的开放区间内有超过 300 万的数据需要检查，所以执行计划选择了后者相对比较小的 94 万作为索引。&lt;/p&gt;
&lt;h3 id="第三步：分析数据"&gt;第三步：分析数据&lt;/h3&gt;
&lt;p&gt;这个表有超过 600 万的数据：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'events'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;

           &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
         &lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InnoDB&lt;/span&gt;
           &lt;span class="k"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6294423&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有的数据开始时间和结束时间均匀分布，索引也没有问题，而且查询除了按月，也有按其他维度（天，日，小时，指定范围）来进行查询，通过添加辅助字段也无法减少开放区间的查询数据量，好像进入了一个死胡同...&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案&lt;/h2&gt;
&lt;p&gt;遇到现有知识无法解决的问题，那就 Google 吧，经过尝试不同的关键字（关于如何选择关键字解决日常搬砖遇到的问题，可以留个坑，以后再写）发现了一篇很详细的文章，和我们遇到的情况是一模一样：
&lt;a href="https://explainextended.com/2009/07/01/overlapping-ranges-mysql/" rel="nofollow" target="_blank"&gt;https://explainextended.com/2009/07/01/overlapping-ranges-mysql/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;文章提到了将时间区间变成二维线段，然后利用 R-Tree (spatial) 索引，通过范围重叠来解决这个性能问题。看起来很不错，那就开动吧。&lt;/p&gt;

&lt;p&gt;添加一个辅助字段并添加索引：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="n"&gt;time_range&lt;/span&gt; &lt;span class="n"&gt;linestring&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;spatial&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index_events_on_time_range&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把时间区间转换成线段更新到这个字段：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;time_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 MBRIntersects 函数来查询重叠：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;MBRIntersects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2017-08-01 00:00:00'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2017-09-01 00:00:00'&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;80 毫秒！相同的查询之前需要 2410 毫秒：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2017-07-01 00:00:00'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'2017-08-01 00:00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;给数据库再加个 trigger，当更新和插入数据时，自动更新 time_range 这个字段：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;events_insert_time_range&lt;/span&gt; &lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;events_update_time_range&lt;/span&gt; &lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题解决收工！&lt;/p&gt;
&lt;h2 id="扩展"&gt;扩展&lt;/h2&gt;
&lt;p&gt;这个问题有很多可以扩展的问题：&lt;/p&gt;

&lt;p&gt;我们之前只会将 R-Tree 用在地理位置的查询，从这个问题可以扩展到时间，IP 地址等其他类似的查询，除了这些范围查询还有其他适合应用的场景吗？&lt;/p&gt;

&lt;p&gt;文章中构建的线段是 (-1,start_time) 到 (1,end_time) 的一条斜线，用位于 X 等于 0 的线段去查询重叠。为什么不能用更直观的方式（类似文章开头的线段示意图），构建一个 (start_time, 0) 到 (end_time,0) 一条线段，然后用 Y 等于 0 的线段去查询重叠呢？&lt;/p&gt;

&lt;p&gt;如果不用数据库 trigger，在 rails 里面有什么方法能够自动更新这个辅助字段呢？甚至更进一步，是否可以删除掉 start_time 和 end_time 这 2 个字段，只用 time_range 这一个字段来满足所有的查询需求呢？&lt;/p&gt;

&lt;p&gt;下篇占坑 性能优化案例分析之三：N:M 的用户动态优化&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Tue, 14 Nov 2017 11:19:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/34571</link>
      <guid>https://ruby-china.org/topics/34571</guid>
    </item>
    <item>
      <title>性能优化案例分析之一：软删除是慢查询的罪魁祸首？</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;在 Rails 项目里面，为了实现软删除，我们经常会使用 acts_as_paranoid 这个 gem。它会给数据库表添加一个 deleted_at 栏位，当删除数据时给这个栏位设置当前时间，查询数据时由于设置了 default_scope，会自动添加&lt;code&gt;deleted_at is null&lt;/code&gt;的查询过滤这些数据，从而实现软删除。这个 gem 由于设置简单，使用方便，被用在很多项目中。&lt;/p&gt;

&lt;p&gt;在给某个使用了这个 gem 的项目做性能优化时，发现很多类似这样的慢查询，平均执行时间超过 2000 毫秒：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from articles where id in (?, ?, ?, ...) and deleted_at is null;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="分析过程"&gt;分析过程&lt;/h2&gt;
&lt;p&gt;对于所有 Mysql 慢查询的分析过程都是类似的&lt;/p&gt;
&lt;h3 id="第一步：EXPLAIN"&gt;第一步：EXPLAIN&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;explain select * from articles where id in (?, ?, ?, ...) and deleted_at is null\G;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到了如下的输出：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  select_type: SIMPLE
        table: articles
   partitions: NULL
         type: ref
possible_keys: PRIMARY,index_articles_on_deleted_at
          key: index_articles_on_deleted_at
      key_len: 6
          ref: const
         rows: 9
     filtered: 0.50
        Extra: Using index condition
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="第二步：理解执行计划"&gt;第二步：理解执行计划&lt;/h3&gt;
&lt;p&gt;从输出的&lt;code&gt;possible_keys&lt;/code&gt;和&lt;code&gt;key&lt;/code&gt;里面可以看到，这个查询有 2 个可能的索引可利用：主键索引和辅助索引，但是最终选择了辅助索引&lt;code&gt;index_articles_on_deleted_at&lt;/code&gt;，和我们设想要用的主键索引&lt;code&gt;PRIMARY&lt;/code&gt;有出入。为了进一步理解 mysql 为什么选择这样一个执行计划，可以把执行计划优化器的追踪参数打开，再次执行扩展分析计划，然后从&lt;code&gt;INFORMATION_SCHEMA.OPTIMIZER_TRACE&lt;/code&gt;表中获取详细信息：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set optimizer_trace="enabled=on";
explain EXTENDED select * from articles where id in (?, ?, ?, ...) and deleted_at is null;
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE\G;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在输出里有这样一段，Mysql 认为使用辅助索引的 IO 花销 16.8，比主键索引的花销 19.666 低，所以最终选择了这个辅助索引：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"considered_access_paths": [
  {
    "access_type": "ref",
    "index": "index_articles_on_deleted_at",
    "rows": 14,
    "cost": 16.8,
    "chosen": true
  },
  {
    "rows_to_scan": 14,
    "access_type": "range",
    "range_details": {
      "used_index": "PRIMARY"
    },
    "resulting_rows": 1.4,
    "cost": 19.666,
    "chosen": false
  }
]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="第三步：分析数据"&gt;第三步：分析数据&lt;/h3&gt;
&lt;p&gt;查看数据表的状态和索引状态：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show table status like 'articles'\G;

           Name: articles
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 3122573

show index from articles where key_name = 'index_articles_on_deleted_at'\G;

        Table: articles
   Non_unique: 1
     Key_name: index_articles_on_deleted_at
 Seq_in_index: 1
  Column_name: deleted_at
    Collation: A
  Cardinality: 122621
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个表有超过 300 万的数据，虽然辅助索引的分组看起来有 122621 那么多，但是都是对删除时间不同的索引，而实际大部分数据都是 NULL，所以这个查询完全利用不到辅助索引优势，导致 Mysql 预估的执行计划 cost 和实际相比有天壤之别。&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案&lt;/h2&gt;
&lt;p&gt;很简单，删除这个索引就好了，这个索引是&lt;code&gt;acts_as_paranoid&lt;/code&gt;文档中推荐创建的，但是实际上大部分的数据模型都不需要建立这个索引，如果说它是罪魁祸首，也勉强说得过去。&lt;/p&gt;
&lt;h2 id="扩展"&gt;扩展&lt;/h2&gt;
&lt;p&gt;虽然这个性能问题看起来很简单，但是每个简单问题背后都有很多更深入问题。
如果从&lt;code&gt;INFORMATION_SCHEMA.OPTIMIZER_TRACE&lt;/code&gt;表中完整信息开始研究，我们会发现这个问题的根源在于 Mysql 5.6.9 之后引入的 optimizer_switch use_index_extensions，更进一步了解会发现辅助索引和主键索引同为 B+TREE，为什么会有不同的查询 cost 呢？&lt;/p&gt;

&lt;p&gt;再扩展一下，如果这个问题不是在 deleted_at 这种类型的索引上，而是发生在其他栏位上，比如这样的慢查询：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from articles where id in (?, ?, ?, ...) and score &amp;gt; 85;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那是否可以通过 Cardinality 和 Rows 来判断有必要给 score 加索引呢，解决方案是否会不一样呢？&lt;/p&gt;

&lt;p&gt;除了删除索引以外，我们还可以用 mysql 语句&lt;code&gt;force index&lt;/code&gt;来强制指定查询使用主键索引，在 rails 里面如何方便地强制索引呢？&lt;/p&gt;

&lt;p&gt;除了添加 deleted_at 这种标志位的方法以外，还没有其他方法实现软删除呢？相比较的优缺点是什么？&lt;/p&gt;

&lt;p&gt;如果以上这些问题你都能回答了，恭喜你，EXP++，LV UP :)&lt;/p&gt;

&lt;p&gt;下篇占坑 性能优化案例分析之二：时间区域查询的性能优化&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Wed, 08 Nov 2017 20:20:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/34540</link>
      <guid>https://ruby-china.org/topics/34540</guid>
    </item>
    <item>
      <title>counter_cache + foreign_key + MySQL = DEADLOCK??</title>
      <description>&lt;p&gt;最近在检查一个项目的日志时，发现了一些奇怪的 deadlock 错误，经过排查之后发现和 counter_cache 以及 foreign_key 相关，记录一下相关的情况。&lt;/p&gt;

&lt;p&gt;功能需求：用户可以对一篇文章点赞，文章需要显示共有多少个赞。为了性能考虑，在 belongs_to 里面设置了 counter_cache，这是很常见的做法。
简化后的 model 代码如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:likes&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Like&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;migration 脚本如下，为了数据一致性考虑，利用了数据库的外键约束，设置了 foreign_key：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateArticles&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:articles&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:content&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:likes_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateLikes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志显示，当多个用户同时对同一篇文章点赞的时候，有概率出现死锁，在 console 里面用多线程模拟一下并发点赞：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;Like&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;article: &lt;/span&gt;&lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跑几次就很容易重现出这个错误：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActiveRecord::StatementInvalid: Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE `articles` SET `likes_count` = COALESCE(`likes_count`, 0) + 1 WHERE `articles`.`id` = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一开始我很不理解为什么这里会出现死锁，因为 Like.create 产生的 sql 很简单：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;`likes`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`article_id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`updated_at`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2017-06-15 06:10:48'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2017-06-15 06:10:48'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;`articles`&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nv"&gt;`likes_count`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`likes_count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;`articles`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照我原先对 mysql 的理解，只有第二句执行 update 的时候，才会对 Article 表的 id 1 记录请求一个 exclusive (X) lock，每个线程都只有一个锁的情况下，只会出现 lockwait，而不是 deadlock。&lt;/p&gt;

&lt;p&gt;经过搜索相关关键字，发现了这个 bug 报告：
&lt;a href="https://bugs.mysql.com/bug.php?id=48652" rel="nofollow" target="_blank"&gt;https://bugs.mysql.com/bug.php?id=48652&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原来由于外键的存在，在执行第一句 insert 的时候，会对 Article 表的 id 1 记录请求一个 shared (S) lock：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If a FOREIGN KEY constraint is defined on a table, any insert, update, or delete that requires the constraint condition to be checked sets shared record-level locks on the records that it looks at to check the constraint. InnoDB also sets these locks in the case where the constraint fails.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2 个线程的 SQL 执行顺序按照这样的时序发生，就会产生死锁：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T1  INSERT 获得 S lock （Article id 1记录）
T2  INSERT 获得 S lock （Article id 1记录）
T1  UPDATE 升级 X lock (等待T2的S lock释放)
T2  UPDATE 升级 X lock (等待T1的S lock释放，死锁发生)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/942c60a0-ef29-4e7e-8349-ff56634c5421.jpg1286488735!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;那如何解决这个问题？有几个选择：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A. 取消外键&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果能够在代码层面保证数据一致性，取消外键是最简单的选择。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. 改用 postgresql&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;对于新系统，我现在都强烈推荐 postgresql，用过了你就不会想回去 mysql。这个外键导致死锁的问题在 9.3 版本之前也存在，但是很快通过新的 Lock 类型解决了，看看 mysql 的那个 bug 报告日期，我都要哭了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. 不用 ActiveRecord 的 counter cache callback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果我们能够将 update counter 的语句在 insert 之前执行，也就不会有死锁的情况发生，改进一下 model 代码如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Like &amp;lt; ApplicationRecord
  belongs_to :article

  before_create do
    article.increment!(:likes_count)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题外话，如果相关模型的并发写很高，即使在没有外键或者 postgresql 的情况下，更新 counter cache 也会成为一个瓶颈，我们还可以选择将计数器更新用 redis 做 buffer，每 N 次再同步到数据库。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Thu, 15 Jun 2017 14:58:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/33235</link>
      <guid>https://ruby-china.org/topics/33235</guid>
    </item>
    <item>
      <title>Ruby 2.4 哈希表 (Hash) 的变化</title>
      <description>&lt;p&gt;Ruby 2.4 对哈希表引入了一些性能改进，这些变化是 Vladimir Makarov 在 2016 年初向 Ruby Hash 提交&lt;a href="https://bugs.ruby-lang.org/issues/12142" rel="nofollow" target="_blank" title=""&gt;补丁&lt;/a&gt;时提出的&lt;/p&gt;

&lt;p&gt;这篇文章简单介绍一下 Ruby 哈希表的基础和 2.4 改变&lt;/p&gt;
&lt;h3 id="Ruby是如何实现哈希表的"&gt;Ruby 是如何实现哈希表的&lt;/h3&gt;
&lt;p&gt;我们从用数组实现哈希表开始，起名叫 TurboHash:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboHash&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:table&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用&lt;a href="/table" class="user-mention" title="@table"&gt;&lt;i&gt;@&lt;/i&gt;table&lt;/a&gt;数组保存条目，然后添加方法去设置和读取条目：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboHash&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 遍历数组查找对应的条目&lt;/span&gt;
    &lt;span class="vi"&gt;@table.find&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="vi"&gt;@table&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写个 benchmark 来对比一下原生 Hash 的性能&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark"&lt;/span&gt;

&lt;span class="n"&gt;legacy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;turbo&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TurboHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_and_find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hash: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;set_and_find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;legacy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"TurboHash: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;set_and_find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很惨，在我的电脑上，比原生的慢了 1000 倍&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       user     system      total        real
Hash:   0.010000   0.000000   0.010000 (  0.004094)
TurboHash:  10.070000   0.050000  10.120000 ( 10.240642)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;瓶颈在于到后面查找条目的时候，每次都要遍历近万个条目，很明显，原生的 Hash 不是通过遍历数组这种方式来实现的。&lt;/p&gt;

&lt;p&gt;在 Ruby 2.4 之前，Ruby 采用的是&lt;a href="https://en.wikipedia.org/wiki/Hash_table#Separate_chaining_with_linked_lists" rel="nofollow" target="_blank" title=""&gt;哈希值链表法&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/1738a6f05673cb5cdefe03ba6eb41c95.svg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一个对象在被放置入哈希表之前，会调用它的 hash 方法得到一个整数，然后对整数取模，获取它所在节点，如果这个节点为空，则在这个节点创建一个链表，将这个对象放置入链表，反之则添加到这个链表。&lt;/p&gt;

&lt;p&gt;我们用这个方法来改一下之前的 TurboHash&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:next&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="vi"&gt;@next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboHash&lt;/span&gt;
  &lt;span class="no"&gt;NUM_BINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:table&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NUM_BINS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="no"&gt;NUM_BINS&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再来运行一下 benchmark，不敢相信自己的眼睛，已经和原生 Hash 一样快了？？只是把原先一个大数组遍历，改成了分开用 11 个格子分开存储，然后用链表遍历，理论上性能应该只是变快了 11 倍左右：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       user     system      total        real
Hash:   0.010000   0.000000   0.010000 (  0.003859)
TurboHash:   0.010000   0.000000   0.010000 (  0.014998)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真相是我这里特别针对 benchmark 的代码作弊了，每次往链表添加对象的时候，都加到了链表的最前面，而 benchmark 代码每次都是读取最后添加对象的 key，不用遍历整个链表，自然会很快，如果把 benchmark 代码修改一下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_and_find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和单数组遍历相比，性能确实只提高了一个数量级，从慢 1000 倍变成慢 100 倍，这也从一个侧面说明了单一或者简单的性能测试代码都是骗人的 ^_^&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       user     system      total        real
Hash:   0.020000   0.000000   0.020000 (  0.019680)
TurboHash:   2.360000   0.010000   2.370000 (  2.375549)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在瓶颈在放置入 Hash 中的对象变多以后，数组对应元素的链表会变长，每次读取的时候，都要遍历。如果随着对象增加进行&lt;a href="https://en.wikipedia.org/wiki/Hash_table#Resizing_by_copying_all_entries" rel="nofollow" target="_blank" title=""&gt;扩容&lt;/a&gt;，可以解决这个瓶颈。&lt;/p&gt;

&lt;p&gt;让我们抄袭一下 Ruby 的源代码，再来改进 TurboHash:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboHash&lt;/span&gt;
  &lt;span class="no"&gt;STARTING_BINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:table&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@max_density&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="vi"&gt;@entry_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="vi"&gt;@bin_count&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;STARTING_BINS&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@bin_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;grow&lt;/span&gt;
    &lt;span class="vi"&gt;@bin_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@bin_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;new_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@bin_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@table.each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;
        &lt;span class="k"&gt;begin&lt;/span&gt;
          &lt;span class="n"&gt;new_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
          &lt;span class="n"&gt;new_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_table&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full?&lt;/span&gt;
    &lt;span class="vi"&gt;@entry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@max_density&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="vi"&gt;@bin_count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="vi"&gt;@bin_count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;grow&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;full?&lt;/span&gt;
    &lt;span class="vi"&gt;@table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;node_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="vi"&gt;@entry_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过一个 entry_count 计数器来统计 Hash 表里的元素个数，当超过一定数量的时候，通过 grow 方法进行扩容，这里初始的 16 个数组个数和平均 5 个密度，都是从 Ruby 源代码中抄过来的。我们再来看一下性能测试结果：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       user     system      total        real
Hash:   0.020000   0.000000   0.020000 (  0.021225)
TurboHash:   0.090000   0.000000   0.090000 (  0.088481)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;已经差不多在一个数量级了，这也基本上就是 Ruby 2.4 之前的 Hash 实现方法，当然实际的原生 Hash 实现代码要比这个复杂很多，还要考虑删除元素，扩容机制的优化，以及一些小细节优化。&lt;/p&gt;
&lt;h3 id="Ruby 2.4 之前的2个小细节优化"&gt;Ruby 2.4 之前的 2 个小细节优化&lt;/h3&gt;
&lt;p&gt;2.0 时候针对小于 7 个元素的 Hash 不额外创建链表结构，直接放置入数组，用数组遍历来实现，我们可以从性能测试代码中看到，达到 7 个元素的时候，性能有个阶梯式的差异：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark/ips'&lt;/span&gt;
&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.2时候，得益于Object#hash方法的改进，hash值更加均匀分布，从质数取模改成了用位操作：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@bin_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Ruby 2.4 Hash的改变"&gt;Ruby 2.4 Hash 的改变&lt;/h3&gt;
&lt;p&gt;Ruby 2.4 采用了&lt;a href="https://en.wikipedia.org/wiki/Hash_table#Open_addressing" rel="nofollow" target="_blank" title=""&gt;Open addressing&lt;/a&gt;改进 Hash 的性能，和之前的实现方法差异主要在于：不再额外维护一个链表结构，每次都是按 hash 索引在数组插入，如果要插入的位置已经有元素存在，那么就找到一个空的位置插入。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/59d8cd8013d80c6a9f47cf0ce00d5620.svg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这种做法称为&lt;a href="https://en.wikipedia.org/wiki/Linear_probing" rel="nofollow" target="_blank" title=""&gt;线性探测&lt;/a&gt;，上图中用的就是最简单的 +1 依次探测，但是这种实现在元素较多的时候，每次插入和读取时，都很容易发生索引已经被占用的情况，然后要依次遍历，性能很差&lt;/p&gt;

&lt;p&gt;Ruby 2.4 采用了随机探测 ( Random Probing ) 来解决这个问题，最简单随机探测实现方法就是加一个伪随机值来探测下一个空位，伪随机值的作用是保证读取的时候随机值和放置时候一样，不会有性能问题。&lt;/p&gt;

&lt;p&gt;类似 Random，伪随机值就是给定一个起始的数值，每次产生的随机数顺序都是一样的：&lt;/p&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 48
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 69
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 26
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 48
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 69
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 2.4 采用了&lt;a href="https://en.wikipedia.org/wiki/Linear_congruential_generator" rel="nofollow" target="_blank" title=""&gt;线性同余方法&lt;/a&gt;来实现一个伪随机值队列：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Xn+1 = (a * Xn + c ) % m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要满足如下 4 个条件，这个算法就可以产生伪随机值队列：&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;m &amp;gt; 0&lt;/li&gt;
&lt;li&gt;0 &amp;lt; a &amp;lt; m&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= c &amp;lt; m&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= X0 &amp;lt; m&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们选取 a＝3, c=1, m=16 来试试看：&lt;/p&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="go"&gt; =&amp;gt; 7
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="go"&gt; =&amp;gt; 9
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="go"&gt; =&amp;gt; 15
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="go"&gt; =&amp;gt; 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在上面的 a,c,m 初始值的选择下，这个队列在获取了 4 次随机值之后就开始循环了，达不到依次遍历所有位置（0 到 m）的需求。&lt;/p&gt;

&lt;p&gt;只要通过选择不同的 a,c,m 初始值，满足一定条件，是可以实现了完整周期线性同余方法（Full Cycle Linear Congruential Generator）&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;m 和 c 互质&lt;/li&gt;
&lt;li&gt;(a - 1) 能被 m 的所有质因数整除&lt;/li&gt;
&lt;li&gt;如果 m 能被 4 整除，那么 (a - 1) 也必须能被 4 整除&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;源码中，是采用了这样的数据 a=5, c=1, m 为 2 的 N 次方（ &lt;a href="https://github.com/ruby/ruby/blob/trunk/st.c#L801" rel="nofollow" target="_blank"&gt;https://github.com/ruby/ruby/blob/trunk/st.c#L801&lt;/a&gt; ）&lt;/p&gt;

&lt;p&gt;我们来试一下：&lt;/p&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 4
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 5
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; 10
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;irb&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正是通过 Open addressing 避免了额外的数据结构，所以 Ruby 2.4 Hash 的内存使用减少了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/3cb30f6e6bd238270340ade24bd06352.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;然后通过完整周期线性同余方法，保证所有对象尽可能在内存中是相邻存放，CPU 在读取内存时，会预读取同一区块的相邻数据到 CPU 缓存，所以 Ruby 2.4 Hash 的性能也有了很大提高：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/01a016cdd47aa652a684c79a6a5bdb56.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;数据来源：&lt;a href="https://rubybench.org/ruby/ruby/releases?result_type=hash_small8" rel="nofollow" target="_blank"&gt;https://rubybench.org/ruby/ruby/releases?result_type=hash_small8&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最后，本文基本上是以下两篇文章的翻译和改编：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.heroku.com/ruby-2-4-features-hashes-integers-rounding#hash-changes" rel="nofollow" target="_blank"&gt;https://blog.heroku.com/ruby-2-4-features-hashes-integers-rounding#hash-changes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blog.redpanthers.co/behind-scenes-hash-table-performance-ruby-2-4/" rel="nofollow" target="_blank"&gt;http://blog.redpanthers.co/behind-scenes-hash-table-performance-ruby-2-4/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最后的最后，如果你有几个小时来阅读 2.4 Hash 的这个&lt;a href="https://bugs.ruby-lang.org/issues/12142" rel="nofollow" target="_blank" title=""&gt;补丁&lt;/a&gt;会发现有很多有趣的内容&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Thu, 16 Mar 2017 14:50:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/32549</link>
      <guid>https://ruby-china.org/topics/32549</guid>
    </item>
    <item>
      <title>Google App Engine 支持 Ruby 了</title>
      <description>&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We’re excited to announce that Ruby runtime on Google App Engine is going beta. Frameworks such as Ruby on Rails and Sinatra make it easy for developers to rapidly build web applications and APIs for the cloud. App Engine provides an easy to use platform for developers to build, deploy, manage, and automatically scale services on Google’s infrastructure.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://cloudplatform.googleblog.com/2016/05/Ruby-on-Google-App-Engine-goes-betaruntime.html" rel="nofollow" target="_blank"&gt;https://cloudplatform.googleblog.com/2016/05/Ruby-on-Google-App-Engine-goes-betaruntime.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Mon, 09 May 2016 10:15:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/29951</link>
      <guid>https://ruby-china.org/topics/29951</guid>
    </item>
    <item>
      <title>自制监控服务接收 NewRelic RPM 数据</title>
      <description>&lt;p&gt;据我了解社区里面很多人都在用 NewRelic，最近空闲时间在折腾自制监控服务来接收 NewRelic rpm 数据，写篇短文分享一下。&lt;/p&gt;

&lt;p&gt;首先我们需要一个服务能够处理 rpm 发出的数据，通过 rpm 的源代码，测试案例和相关资料，可以一个个 api 地逆向工程出服务器端的代码：
&lt;a href="https://github.com/quake/rpm_selfhost/blob/master/rpm_selfhost.rb" rel="nofollow" target="_blank"&gt;https://github.com/quake/rpm_selfhost/blob/master/rpm_selfhost.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前粗糙地实现了 3 个接口：analytic_event_data / metric_data / error_data，通过 rackup 命令来启动这个服务。&lt;/p&gt;

&lt;p&gt;然后我们需要这个服务能接收到 rpm 上报的数据，通过查看 rpm 的源代码，有 2 种可能的实现方式：
1.修改配置文件：config/newrelic.yml，让原本发给 NewRelic 服务器的数据，直接发给自己的服务&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host: localhost
port: 9292
api_host: localhost
api_port: 9292
ssl: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个方法的好处是实现简单，方便在测试环境调试，缺点是 NewRelic 本身无法再收到应用的数据。&lt;/p&gt;

&lt;p&gt;2.修改配置文件：config/newrelic.yml，记录上报数据的日志：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;audit_log:
  enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过日志处理脚本将日志导入：
&lt;a href="https://github.com/quake/rpm_selfhost/blob/master/log_import.rb" rel="nofollow" target="_blank"&gt;https://github.com/quake/rpm_selfhost/blob/master/log_import.rb&lt;/a&gt;
这个方法的好处是 NewRelic 也可以收到数据，缺点是非实时&lt;/p&gt;

&lt;p&gt;这个服务存储用的 InfluxDB，按官方文档安装，然后创建一个名叫 collector 的数据库即可。&lt;/p&gt;

&lt;p&gt;在数据接收完毕以后，我们可以通过 Grafana 这个工具来做展示，按官方文档安装以后，开始制作自己的 Dashboard，比如说 Throughput，我们写这样的查询：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT count("duration") AS "duration" FROM "analytic_event_data" WHERE "type" = 'Transaction' and "name" !~ /^Controller\/Middleware\// AND $timeFilter GROUP BY time($interval)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后设置相关的参数，比如说 Group by time interval &amp;gt;60s&lt;/p&gt;

&lt;p&gt;再比如说请求的响应时间：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 1000 * mean("duration")  FROM "analytic_event_data" WHERE $timeFilter and type = 'Transaction' and "name" !~ /^Controller\/Middleware\// GROUP BY time($interval)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以看到还不错的图表了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/d6d7447d07fc97197fd5b55133d3fee2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;以上这 2 个图表可以导入 Grafana 的 Dashboard json 文件直接看到：
&lt;a href="https://github.com/quake/rpm_selfhost/blob/master/grafana.json" rel="nofollow" target="_blank"&gt;https://github.com/quake/rpm_selfhost/blob/master/grafana.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前这个项目只实现了几个最简陋的图表，还只是一个玩具，欢迎有兴趣的同学们来 fork 这个项目（ &lt;a href="https://github.com/quake/rpm_selfhost" rel="nofollow" target="_blank"&gt;https://github.com/quake/rpm_selfhost&lt;/a&gt; ），有几个点可以玩：
(已完成)1. 实现更多的 api，比如说 connect 这个 api，需要记录上报数据的 host 信息，返回给 rpm agent 一个 agent_run_id(目前是写死 123)，建立 host 和 agent_run_id mapping 关系，这样才能实现多台主机的情况下按主机来分开统计。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;查看 rpm 客户端源码，理解上报数据的逻辑关系，通过 Grafana 展现更多的图表&lt;/li&gt;
&lt;li&gt;实现代理模式，将上报 NewRelic 的数据在本地同时保存一份&lt;/li&gt;
&lt;li&gt;将项目 gem 化，数据库信息、端口信息配置化，通过一个命令即可启动服务&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;参考资料
&lt;a href="https://mic-kul.com/2015/10/24/garage-made-self-hosted-newrelic-collector-using-ruby-sinatra-grafana-and-influxdb/" rel="nofollow" target="_blank"&gt;https://mic-kul.com/2015/10/24/garage-made-self-hosted-newrelic-collector-using-ruby-sinatra-grafana-and-influxdb/&lt;/a&gt;
&lt;a href="https://ruby-china.org/topics/23470" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/23470&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=================12 月 1 号更新的分割线==================================
完成了 connect api，能够支持多台主机和多个应用，进行过滤展示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/a3df4b592573230e714191c5aa104004.png" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Fri, 13 Nov 2015 17:19:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/28055</link>
      <guid>https://ruby-china.org/topics/28055</guid>
    </item>
    <item>
      <title>[上海] 蝉游记 / 携程周末 招聘 Ruby 工程师</title>
      <description>&lt;h2 id="团队介绍"&gt;团队介绍&lt;/h2&gt;
&lt;p&gt;「蝉游记」( &lt;a href="http://chanyouji.com" rel="nofollow" target="_blank"&gt;http://chanyouji.com&lt;/a&gt; ) 创立于 2012 年，13 年被携程投资，14 年底被携程全资收购。并入携程后，成立了一个新的微型事业部，由做蝉游记和玩票产品的蝉小队，以及做「携程周末」（&lt;a href="https://itunes.apple.com/cn/app/id986276381?mt=8" rel="nofollow" target="_blank"&gt;https://itunes.apple.com/cn/app/id986276381?mt=8&lt;/a&gt;）这款新产品的城市猎人小队构成。&lt;/p&gt;

&lt;p&gt;携程周末是今年启动的新项目，5 月初发布，定位于“城市玩乐指南”。这也是接下来主打的产品。目前开通了北上广杭四个城市，接下来会开通深圳和成都。&lt;/p&gt;

&lt;p&gt;「蝉游记」发布两年半，定位于“旅行资讯与工具”，2014 年至今在苹果线下门店被选为 iPhone 样机内置应用，算是蝉小队的代表作。5 月初发布的蝉游记 5.0(iOS 版) 合并了另一款 App 蝉游攻略，提供更强大的旅行攻略。&lt;/p&gt;

&lt;p&gt;除了主力产品，蝉小队还有一些玩票 App，包括已经发布的形态奇特的「生辰」，和列入下半年研发计划，伺机而动的另外几款玩票产品。&lt;/p&gt;
&lt;h2 id="工作职责"&gt;工作职责&lt;/h2&gt;
&lt;p&gt;为上述 App 提供管理后台以及 API 的设计、开发、维护&lt;/p&gt;
&lt;h2 id="基本要求"&gt;基本要求&lt;/h2&gt;
&lt;p&gt;一年以上 RoR 开发经验
熟悉关系型数据库
熟悉常见的业务建模方法&lt;/p&gt;
&lt;h2 id="加分项"&gt;加分项&lt;/h2&gt;
&lt;p&gt;熟悉前端技术
熟悉 Linux 系统的部署和维护
有业务代码重构经验&lt;/p&gt;
&lt;h2 id="工作地点"&gt;工作地点&lt;/h2&gt;
&lt;p&gt;上海福泉路 99 号，靠近地铁 2 号线淞虹路站，交通便利&lt;/p&gt;
&lt;h2 id="福利待遇"&gt;福利待遇&lt;/h2&gt;
&lt;p&gt;月薪范围在 10k~20k，每年 14+ 月，具体面谈
简历请发送：quake@chanyouji.com
来信请附上你的 ruby-china 帐号/github 帐号/个人博客地址/Twitter or 微博地址（以上都没有的话......&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Mon, 01 Jun 2015 16:06:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/25835</link>
      <guid>https://ruby-china.org/topics/25835</guid>
    </item>
    <item>
      <title>Ruby 中的函数式编程</title>
      <description>&lt;p&gt;为培训由其他编程语言转到 Ruby 的程序员，写了一些 PPT，其中涉及到了一些函数式编程，反馈还不错，于是抽取出来写了一篇短文。&lt;/p&gt;
&lt;h2 id="什么是函数式编程"&gt;什么是函数式编程&lt;/h2&gt;
&lt;p&gt;跳过...请自己 Google&lt;/p&gt;
&lt;h2 id="函数式编程有什么好处"&gt;函数式编程有什么好处&lt;/h2&gt;
&lt;p&gt;会让你的代码看起来更屌（实际并不...
会让你的工资增加（做梦...
会让你写的程序性能更好（一般来说没差别...&lt;/p&gt;

&lt;p&gt;到底有什么好处...请自己 Google&lt;/p&gt;
&lt;h2 id="请先做道题"&gt;请先做道题&lt;/h2&gt;
&lt;p&gt;奇偶归一猜想（英语：Collatz conjecture），是指对于每一个正整数，如果它是奇数，则对它乘 3 再加 1，如果它是偶数，则对它除以 2，如此循环，最终都能够得到 1。
如 n = 6，根据上述数式，得出序列 6, 3, 10, 5, 16, 8, 4, 2, 1。(步骤中最高的数是 16，共有 8 个步骤)
如 n = 8，根据上述数式，得出序列 8, 4, 2, 1。(步骤中最高的数是 8，共有 3 个步骤)
如 n = 11，根据上述数式，得出序列 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1。(步骤中最高的数是 52，共有 14 个步骤)&lt;/p&gt;

&lt;p&gt;给定一个数组，对这个数组里面的每个数字做奇偶归一操作，找出步骤最多的一个序列，比如输入 [4, 6, 8, 11]
返回 [11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]&lt;/p&gt;
&lt;h2 id="Ruby中的函数式编程"&gt;Ruby 中的函数式编程&lt;/h2&gt;&lt;h2 id="不更新变量"&gt;不更新变量&lt;/h2&gt;
&lt;p&gt;array 的 &amp;lt;&amp;lt; vs +&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hash 的 []= vs merge&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
  &lt;span class="nb"&gt;hash&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;foo: &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="不依赖外部变量"&gt;不依赖外部变量&lt;/h2&gt;
&lt;p&gt;一个例子，常见其他语言转过来的程序员写的 ruby 代码，将一个数组中的字符串转换成大写输出：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"china"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"usa"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;countries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;countries&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; ["CHINA", "USA"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依赖了外部变量（countries），同时还对变量做了更新（&amp;lt;&amp;lt;）&lt;/p&gt;

&lt;p&gt;函数式编程，使用 map：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"china"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"usa"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; ["CHINA", "USA"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再一个例子，加总数组中字符串长度：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"china"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"usa"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数式编程，使用 inject：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"china"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"usa"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;memo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以使用语法糖，简写方式：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"china"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"usa"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;了解 Enumerable/Array/Hash 的各种方法：map/inject/select/reject/scan/each/each_pair/each_cons...会让你写出不一样的代码。&lt;/p&gt;
&lt;h2 id="Currying"&gt;Currying&lt;/h2&gt;
&lt;p&gt;另外一个比较好玩的特性，举个例子，我们可以假装发明了操作符前置的中文编程：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;计算&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;加&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;计算&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;减&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;计算&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;乘&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;计算&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;除&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;计算&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;加&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;乘&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 42&lt;/span&gt;

&lt;span class="n"&gt;加1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;加&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;加1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;# =&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Ruby 里面 proc 调用可以用 proc.call, proc.(), 或者 proc[], 我比较喜欢 proc[] 这种方式。&lt;/p&gt;
&lt;h2 id="回到最初的题目"&gt;回到最初的题目&lt;/h2&gt;
&lt;p&gt;首先写一个函数，来做奇偶判断对应的操作：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;even?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再写个函数，来判断是否到 1，返回序列：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后写个函数，将输入的数组做 map，然后取序列最大的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]}.&lt;/span&gt;&lt;span class="nf"&gt;max_by&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f2&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你最初写的代码会是怎样？对比一下，是否函数式编程真的体现了”describe what to do, rather than how to do it“&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Thu, 30 Apr 2015 17:55:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/25389</link>
      <guid>https://ruby-china.org/topics/25389</guid>
    </item>
    <item>
      <title>程序员的好玩具 - Lego EV3</title>
      <description>&lt;p&gt;本来准备这周二上海 Tuesday 上将 EV3 带过去的，想秀一下用 ruby 代码来控制 EV3 机器人，可惜时间冲突没去成，发个评测补一下：&lt;/p&gt;

&lt;p&gt;&lt;a href="http://knewone.com/things/lego-ev3/reviews/54dd72dd31302d357f5d0000" rel="nofollow" target="_blank"&gt;http://knewone.com/things/lego-ev3/reviews/54dd72dd31302d357f5d0000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;年后有空再来秀。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Fri, 13 Feb 2015 11:45:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/24246</link>
      <guid>https://ruby-china.org/topics/24246</guid>
    </item>
    <item>
      <title>Redis 作为缓存服务器的配置</title>
      <description>&lt;p&gt;随着 redis 的发展，越来越多的架构用它取代了 memcached 作为缓存服务器的角色，它有几个很突出的特点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;除了 Hash，还提供了 Sorted Set, List 等数据结构&lt;/li&gt;
&lt;li&gt;可以持久化到磁盘&lt;/li&gt;
&lt;li&gt;支持 cluster (3.0)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;它的性能和 memcached 不相上下，再加上流行的其他组件（比如队列）也会用到 redis，从架构简单出发，已经没有必要混用 redis 和 memcached 了。&lt;/p&gt;

&lt;p&gt;写篇短文介绍一下用 redis 作为缓存服务器配置时候需要注意几个点。&lt;/p&gt;
&lt;h2 id="Redis配置"&gt;Redis 配置&lt;/h2&gt;
&lt;p&gt;作为缓存服务器，如果不加以限制内存的话，就很有可能出现将整台服务器内存都耗光的情况，可以在 redis 的配置文件里面设置：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 限定最多使用1.5GB内存
maxmemory 1536mb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果内存到达了指定的上限，还要往 redis 里面添加更多的缓存内容，需要设置清理内容的策略：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 设置策略为清理最少使用的key对应的数据
maxmemory-policy allkeys-lru
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;清理策略有多种，redis 的官方文档有一篇很详细的说明： &lt;a href="http://redis.io/topics/lru-cache" rel="nofollow" target="_blank"&gt;http://redis.io/topics/lru-cache&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Redis监控"&gt;Redis 监控&lt;/h2&gt;
&lt;p&gt;redis 提供了 INFO 这个命令，能够随时监控服务器的状态，只用 telnet 到对应服务器的端口，执行命令即可：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telnet localhost 6379
info
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在输出的信息里面有这几项和缓存的状态比较有关系：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyspace_hits:14414110
keyspace_misses:3228654
used_memory:433264648
expired_keys:1333536
evicted_keys:1547380
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过计算 hits 和 miss，我们可以得到缓存的命中率：14414110 / (14414110 + 3228654) = 81% ，一个缓存失效机制，和过期时间设计良好的系统，命中率可以做到 95% 以上，对于整体性能提升是很大的。
used_memory,expired_keys,evicted_keys 这 3 个信息的具体含义，redis 的官方也有一篇很详细的说明： &lt;a href="http://redis.io/commands/info" rel="nofollow" target="_blank"&gt;http://redis.io/commands/info&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;有个 ruby gem 叫 redis-stat，它利用 INFO 命令展现出更直观的信息报表，推荐：
&lt;a href="https://github.com/junegunn/redis-stat" rel="nofollow" target="_blank"&gt;https://github.com/junegunn/redis-stat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/82396805d893ad0af30845d4a2abd15c.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="优化Rails的缓存配置"&gt;优化 Rails 的缓存配置&lt;/h2&gt;
&lt;p&gt;Rails 在用 redis 作为缓存的时候，配置很简单，官方文档是用 schema 的方式来写的：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.cache_store = :redis_store, "redis://localhost:6379/0/cache"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于实在太简单了，很多人就直接用这个默认设置了，但实际上还有一些很有用的参数可以通过 hash options 的方式来写，比如压缩超过 32K 的数据压缩以后再放入缓存，再比如设置默认所有的 key 失效时间为 8 小时：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.cache_store = :redis_store, {:host =&amp;gt; 'redis.server', :port =&amp;gt; 6379, :compress =&amp;gt; true, :expires_in =&amp;gt; 8.hours, :compress_threshold =&amp;gt; 32.kilobytes}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用一个实际案例来作为例子，一台 redis 缓存服务器在优化配置之前，占用 4.2G 左右的内存，缓存命中率在 70% 左右。
我们先在服务器上执行 BGSAVE 命令，将内存 dump 下来，然后用 &lt;a href="https://github.com/sripathikrishnan/redis-rdb-tools" rel="nofollow" target="_blank"&gt;https://github.com/sripathikrishnan/redis-rdb-tools&lt;/a&gt; 这个工具，将 dump 的数据，解析成 csv 文件：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rdb -c memory /var/redis/6379/dump.rdb &amp;gt; memory.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 excel 打开分析，按 key 进行排序和统计，我们可以看到哪一些类型的缓存在服务器上最多，调整这个类型缓存的失效时间和失效机制，再通过 redis-stat 来观察进行微调，提高整体的命中率。
通过分析占用内存比较大的 key，发现有 30% 左右的相同类型 key，用了 95% 的内存，这些缓存大部分是 html 的片段缓存，通过设置 compress_threshold 和 compress 参数，让整体内存占用从 4.2G，下降到了 1.3G。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Wed, 19 Nov 2014 15:49:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/22761</link>
      <guid>https://ruby-china.org/topics/22761</guid>
    </item>
    <item>
      <title> 性能监控的好工具 - NewRelic 简介</title>
      <description>&lt;p&gt;我们蝉游记服务器端性能监控一直用 &lt;a href="http://newrelic.com/" rel="nofollow" target="_blank"&gt;http://newrelic.com/&lt;/a&gt; ，用它的免费版本 N 长时间，有必要为好工具义务宣传一下。&lt;/p&gt;

&lt;p&gt;首先你需要在网站上注册一个新帐号，根据服务器端的应用框架选择安装对应的插件，它提供了很多常见应用框架插件，以 Rails 为例子，只需要在 Gemfile 配置，执行 bundle install 即可：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'newrelic_rpm'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后下载对应的 newrlic.yml 配置文件，放入到应用目录，进行一些参数的调整。将应用重新部署以后，等几分钟，让插件收集到性能相关数据，再去访问 NewRelic 网站，就可以看到各种图表了。&lt;/p&gt;

&lt;p&gt;首先需要关注的是请求的响应时间图表，用这个图表可以对请求在服务器端耗时有个整体印象：
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6870/b315e203-5433-3aa0-8a28-1ef87c9b70c1.png" title="" alt=""&gt;
从图表上可以看到，在这个时间段，请求的平均耗时是 52ms，同时可以看到每个请求的 Ruby 代码和数据库执行时间占据了绝大部分，还有少量的外部服务调用时间（比如第 3 方 Oauth 或者 API)。由于我们使用 OOB GC，所以在图表上几乎没有 GC 的时间。&lt;/p&gt;

&lt;p&gt;另外右上角有一个 3.58s 的浏览器时间，这个是指用户访问网页，从请求发出，到整个页面完全加载完成（包括图片，css，js 等）。&lt;/p&gt;

&lt;p&gt;第二个图表是 Apdex (Application Performance Index)，从这里可以看到大部分用户是否满意你的应用响应速度：
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6878/2a4f6da4-3dac-3cd4-bd65-f05075428660.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从图表可以看到，99% 的用户非常满意请求响应时间（在我们的应用里面，大部分请求是客户端调用 api），93% 的用户非常满意页面加载完成的时间。我们用的指标是 NewRelic 默认设置的 500ms 和 7s，你还可以自己进行调整。&lt;/p&gt;

&lt;p&gt;第 3 个图表是吞吐量
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6880/0dc40e73-6f1b-3599-ae73-6ade99623007.png" title="" alt=""&gt;
可以看出在这半个小时的区间，平均每分钟有 1050 个请求。用这个图表，通过調整时间段（，了解整个应用什么时候是高峰，什么时候是低谷，方便将一些批处理，备份等任务放在访问低谷的时间段进行。还有当用户报告无法访问的时候，可以通过查看吞吐量是否有急剧下降，来判断是个例还是整体故障，来确定解决问题的优先级。免费版本的 NewRelic，只能查看过去 24 小时的数据，升级到付费帐号，可以查看所有的历史数据。&lt;/p&gt;

&lt;p&gt;第 4 个图表是根据请求的时间和请求的次数，列出一个最耗时的请求
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6892/ffdf1aba-03e5-3962-aab7-5c807272f1bd.png" title="" alt=""&gt;
我们可以根据这个排列顺序，来考虑对于访问量大，同时又耗时的请求进行重点性能优化。
点击具体的请求，还可以看到请求耗时的分布情况：
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6894/8c9b5712-5bf8-38b8-9905-df348e632a4d.png" title="" alt=""&gt;
从图表上可以看到这个请求，在渲染 json 数据耗费了比较多的时间，另外在 ActiveRecord 的查询上也耗费了一些时间，如果要优化的话，就可以从渲染结果加片段缓存，或者查询优化入手。升级到付费版本，还能查看到具体的 sql 语句执行情况，如果有 slow query，还能显示 explain 的结果。&lt;/p&gt;

&lt;p&gt;第 5 个图表，是错误率
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6898/101f9130-11e4-341e-b3df-e3054252fd69.png" title="" alt=""&gt;
在我们应用中引发错误的大部分是一些爬虫 404，还有一些是异常数据没有处理好导致，这里就不详细介绍了。这个图表对于用户反馈错误定位也是很有帮助的。&lt;/p&gt;

&lt;p&gt;第 6 个图表，是应用性能分布到各个服务器状态
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6900/3a4f6585-e5a6-3a4a-bf1a-56049d3af2d9.png" title="" alt=""&gt;
对于了解服务器资源和扩容计划很有帮助。顺便说一个实际遇到的事情，发现过某台主机（我们用的云主机）的响应时间就是比其他台要慢 50% 以上，对比发现这台的 CPU 和 Disk IO 都比其他台要弱，后来报告给云主机服务商，通过迁移到了新的物理机解决了。&lt;/p&gt;

&lt;p&gt;除了上述的这些性能图表，NewRelic 也提供了很多监控的选项，比如 ping，内存/存储警报等，可以将报警发送到邮件，或者推送到移动客户端：
&lt;img src="http://dl2.iteye.com/upload/attachment/0102/6904/48479e62-88b9-3d0b-b98d-0be4d6c6c135.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里就简单介绍一下它免费版本的少量功能，更多的功能留待大家去玩吧。最后还要提一下最喜欢的 scalability report 和 database report，可以申请试用付费帐号来体验，是我见过最赞的报表了。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Fri, 31 Oct 2014 15:38:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/22379</link>
      <guid>https://ruby-china.org/topics/22379</guid>
    </item>
    <item>
      <title>Ruby 程序员写 Swift 毫无压力</title>
      <description>&lt;p&gt;花半个小时看了一下 Swift 的文档，很开心地发现不需要学习它的语法，各位熟悉 ruby 语法的程序员就算不看文档，阅读 swift 代码也应该毫无压力，如果你之前熟悉 apple framework 的各种 api，那就赶紧转移到 swift 开发 ios 应用吧，因为终于可以抛弃 Objective C 这个上世纪的语言了 :)&lt;/p&gt;

&lt;p&gt;swift 和 ruby 几个简单的对比：&lt;/p&gt;

&lt;p&gt;swift 用 let 定义常量，用 var 定义变量
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;maximumNumberOfLoginAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;currentLoginAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby 无关键字，用大写字母代表常量
ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;MAXIMUM_NUMBER_OF_LOGIN_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;currentLoginAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义多个变量
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字符串
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The current value of friendlyWelcome is &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;friendlyWelcome&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"The current value of friendlyWelcome is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;friendlyWelcome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;N 进制数字和各种数字格式，完全和 ruby 是一模一样...
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;decimalInteger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;binaryInteger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mb"&gt;0b10001&lt;/span&gt; &lt;span class="c1"&gt;// 17 in binary notation&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;octalInteger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mo"&gt;0o21&lt;/span&gt; &lt;span class="c1"&gt;// 17 in octal notation&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hexadecimalInteger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x11&lt;/span&gt; &lt;span class="c1"&gt;// 17 in hexadecimal notation&lt;/span&gt;


&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;decimalDouble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.1875&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;exponentDouble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.21875e1&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hexadecimalDouble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xC.3p0&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;paddedDouble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;000123.456&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;oneMillion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;justOverOneMillion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1_000_000.000_0&lt;/span&gt;&lt;span class="mo"&gt;00_1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tuples, swift 需要定义类型组合，常用在多参数返回，而 ruby 则直接支持数组
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;http404Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not Found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http404Error&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;justTheStatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http404Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;http404Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Not Found"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http404Error&lt;/span&gt;
&lt;span class="n"&gt;justTheStatusCode&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http404Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;条件绑定，这个很方便的语法糖，已经成为现代语言的标配：
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;actualNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; has an integer value of &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;actualNumber&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; could not be converted to an integer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;actualNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has an integer value of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;actualNumber&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;possibleNumber&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; could not be converted to an integer"&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遍历数组
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shoppingList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shoppingList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Item &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shoppingList&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#ruby的数组是Enumerable，遍历更常用each/each_with_index等方法&lt;/span&gt;
&lt;span class="n"&gt;shoppingList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"Item &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dictionary
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;airports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"TYO"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DUB"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Dublin"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;airports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"LHR"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;airportCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;airportName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;airports&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;airportCode&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;airportName&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;airports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;TYO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DUB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Dublin"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;airports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:LHR&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"London"&lt;/span&gt;
&lt;span class="n"&gt;airports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;airportCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;airportName&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;airportCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;airportName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Range
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; times 5 is &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 用..表示闭合区间，...表示开合区间
ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times 5 is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Closures
swift&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;digitNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Zero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"One"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Three"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Four"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Five"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Six"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Seven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Eight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Nine"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;510&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digitNames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// strings is inferred to be of type String[]&lt;/span&gt;
&lt;span class="c1"&gt;// its value is ["OneSix", "FiveEight", "FiveOneZero"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;digitNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Zero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"One"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Three"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Four"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Five"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Six"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Seven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Eight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Nine"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;510&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digitNames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>quakewang</author>
      <pubDate>Tue, 03 Jun 2014 10:07:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/19689</link>
      <guid>https://ruby-china.org/topics/19689</guid>
    </item>
    <item>
      <title>总结 Web 应用中常用的各种 Cache</title>
      <description>&lt;p&gt;总结 web 应用中常用的各种 cache&lt;/p&gt;

&lt;p&gt;cache 是提高应用性能重要的一个环节，写篇文章总结一下用过的各种对于动态内容的 cache。
文章以 Nginx，Rails，Mysql，Redis 作为例子，换成其他 web 服务器，语言，数据库，缓存服务都是类似的。
以下是 3 层的示意图，方便后续引用：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
                          +-------+
1                         | Nginx |
                          +-+-+-+-+
                            | | |
            +---------------+ | +---------------+
            |                 |                 |
        +---+---+         +---+---+         +---+---+
2       |Unicorn|         |Unicorn|         |Unicorn|
        +---+---+         +---+---+         +---+---+
            |                 |                 |
            |                 |                 |
            |             +---+---+             |
3           +-------------+  D B  +-------------+
                          +-------+

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="1. 客户端缓存"&gt;1. 客户端缓存&lt;/h2&gt;
&lt;p&gt;一个客户端经常会访问同一个资源，比如用浏览器访问网站首页或查看同一篇文章，或用 app 访问同一个 api，如果该资源和他之前访问过的没有任何改变，就可以利用 http 规范中的 304 Not Modified 响应头 ( &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5" rel="nofollow" target="_blank"&gt;http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5&lt;/a&gt; )，直接用客户端的缓存，而无需在服务器端再生成一次内容。
在 Rails 里面内置了 fresh_when 这个方法，一行代码就可以完成：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="ss"&gt;:last_modified&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@article.updated_at.utc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:etag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下次用户再访问的时候，会对比 request header 里面的 If-Modified-Since 和 If-None-Match，如果相符合，就直接返回 304，而不再生成 response body。&lt;/p&gt;

&lt;p&gt;但是这样会遇到一个问题，假设我们的网站导航有用户信息，一个用户在未登陆专题访问了一下，然后登陆以后再访问，会发现页面上显示的还是未登陆状态。或者在 app 访问一篇文章，做了一下收藏，下次再进入这篇文章，还是显示未收藏状态。解决这个问题的方法很简单，将用户相关的变量也加入到 etag 的计算里面：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="ss"&gt;:etag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vi"&gt;@article.cache_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="ss"&gt;:etag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vi"&gt;@article.cache_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user_favorited&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外提一个坑，如果 nginx 开启了 gzip，对 rails 执行的结果进行压缩，会将 rails 输出的 etag header 干掉，nginx 的开发人员说根据 rfc 规范，对 proxy_pass 方式处理必须这样（因为内容改变了），但是我个人认为没这个必要，于是用了粗暴的方法，直接将 src/http/modules/ngx_http_gzip_filter_module.c 这个文件里面的这行代码注释掉，然后重新编译 nginx：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;//ngx_http_clear_etag(r);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者你可以选择不改变 nginx 源代码，将 gzip off 掉，将压缩用 Rack 中间件来处理：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Deflater&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了在 controller 里面指定 fresh_when 以外，rails 框架默认使用 Rack::ETag middleware，它会自动给无 etag 的 response 加上 etag，但是和 fresh_when 相比，自动 etag 能够节省的只是客户端时间，服务器端还是一样会执行所有的代码，用 curl 来对比一下。
Rack::ETag 自动加入 etag：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -v http://localhost:3000/articles/1
&amp;lt; Etag: "bf328447bcb2b8706193a50962035619"
&amp;lt; X-Runtime: 0.286958
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
&amp;lt; X-Runtime: 0.293798
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 fresh_when：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
&amp;lt; X-Runtime: 0.033884
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2. Nginx缓存"&gt;2. Nginx 缓存&lt;/h2&gt;
&lt;p&gt;有一些资源可能会被调用很多，又无关用户状态，并且很少改变，比如新闻 app 上的列表 api，购物网站上 ajax 请求分类菜单，可以考虑用 Nginx 来做缓存。
主要有 2 种实现方法：
A. 动态请求静态文件化
在 rails 请求完成以后，将结果保存成静态文件，后续请求就会直接由 nginx 提供静态文件内容，用 after_filter 来实现一下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CategoriesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;after_filter&lt;/span&gt; &lt;span class="ss"&gt;:generate_static_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:only&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_static_file&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外我们需要在任何分类更新的时候，删除掉这个文件，避免缓存不刷新的问题：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:delete_static_file&lt;/span&gt;
  &lt;span class="n"&gt;after_destroy&lt;/span&gt; &lt;span class="ss"&gt;:delete_static_file&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_static_file&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rails 4 之前，处理这种生成静态文件缓存可以用内置的 caches_page，rails 4 之后变成了一个独立 gem actionpack-page_caching，和手工代码对比一下，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CategoriesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;caches_page&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="n"&gt;expire_page&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'index'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果只有一台服务器，这个方法简单又实用，但是如果有多台服务器，就会出现更新分类只能刷新自己本身这台服务器缓存的问题，可以用 nfs 来共享静态资源目录解决，或者用第 2 种：&lt;/p&gt;

&lt;p&gt;B. 静态化到集中缓存服务
首先我们得让 Nginx 有直接访问缓存的能力：&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;redis_server_ip:6379&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;ruby_backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;unicorn_server_ip1&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;unicorn_server_ip2&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/categories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$redis_key&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;default_type&lt;/span&gt;   &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;redis_pass&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@httpapp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="s"&gt;@httpapp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://ruby_backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nginx 首先会用请求的 uri 作为 key 去 redis 里面获取，如果获取不到（404）就转发给 unicorn 进行处理，然后改写 generate_static_file 和 delete_static_file 方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;redis_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样除了集中管理以外，还能够设置缓存的失效时间，对于一些更新无时效性要求的数据，就可以不用处理刷新机制，简单地固定时间刷新一次：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'categories'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3. 整页缓存"&gt;3. 整页缓存&lt;/h2&gt;
&lt;p&gt;Nginx 缓存在处理带参数资源或者有用户状态的请求时候，就非常难以处理，这个时候可以用到整页缓存。
比如说分页请求列表，我们可以将 page 参数加入到 cache_path：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CategoriesController&lt;/span&gt;
  &lt;span class="n"&gt;caches_action&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:cache_path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"categories/index/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如说我们只需要针对 rss 输出进行缓存 8 小时：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&lt;/span&gt;
  &lt;span class="n"&gt;caches_action&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:if&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rss?&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再比如说对于非登陆用户，我们可以缓存首页：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt;
  &lt;span class="n"&gt;caches_action&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:if&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;user_signed_in?&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="4. 片段缓存"&gt;4. 片段缓存&lt;/h2&gt;
&lt;p&gt;如果说前面 2 种缓存能够用到的场景有限，那么片段缓存是适用性最广的。&lt;/p&gt;

&lt;p&gt;场景 1：我们需要在每个页面一段广告代码，用来显示不同广告，如果没有使用片段缓存，那么每个页面都会要去查询广告的代码，并且花费一定时间去生成 html 代码：&lt;/p&gt;
&lt;pre class="highlight slim"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;advert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Advert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;controller_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:enable&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.ad&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;advert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加了片段缓存以后，就可以少去这个查询：&lt;/p&gt;
&lt;pre class="highlight slim"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="s2"&gt;"adverts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;controller_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;advert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Advert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;controller_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:enable&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.ad&lt;/span&gt;
      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;advert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;场景 2：阅读文章，文章的内容可能比较长时间都不会改变，经常变化可能是文章评论，就可以对文章主体部分加上片段缓存：&lt;/p&gt;
&lt;pre class="highlight slim"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="s2"&gt;"articles/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@article.id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@article.updated_at.to_i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.article&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@article.content.markdown2html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;节约了生成 markdown 语法转换到 html 时间，这里用文章最后更新时间作为 cache key 的一部分，文章内容如果有改变，缓存自动失效，默认 activerecord 的 cache_key 方法也是用 updated_at，你也可以加入更多的参数，比如 article 上有评论数的 counter cache，更新评论数的时候不会更新文章时间，可以将这个 counter 也加入到 key 的一部分&lt;/p&gt;

&lt;p&gt;场景 3：复杂页面结构的生成
数据结构比较复杂的页面，在生成的时候避免不了大量的查询和 html 渲染，用片段缓存，可以将这部分时间大大地节约，以我们网站游记页面 &lt;a href="http://chanyouji.com/trips/109123" rel="nofollow" target="_blank"&gt;http://chanyouji.com/trips/109123&lt;/a&gt; （请允许小小地打个广告，带点流量）来说：
需要获取天气数据，照片数据，文本数据等，同时还要生成 meta，keyword 等 seo 数据，而这些内容又是和其他动态内容交叉，片段缓存就可以分开多个：&lt;/p&gt;
&lt;pre class="highlight slim"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="s2"&gt;"trips/show/seo/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@trip.fragment_cache_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;trip_name&lt;/span&gt; &lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;
  &lt;span class="nt"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"keywords"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt;
  &lt;span class="nt"&gt;div&lt;/span&gt;
    &lt;span class="nc"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="s2"&gt;"trips/show/viewer/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@trip.fragment_cache_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@trip.eager_load_all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小贴士，我在 trip 对象里面加了一个 eager_load_all 方法，缓存没有命中的时候，查询的时候避免出现 n+1 问题：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eager_load_all&lt;/span&gt;
  &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Associations&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Preloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:trip_days&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:weather_station_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:nodes&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:notes&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:photo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:audio&lt;/span&gt;&lt;span class="p"&gt;]]]}).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小技巧 1：带条件的片段缓存
和 caches_action 不同，rails 自带的片段缓存是不支持条件的，比如说我们想未登陆用户给他用片段缓存，而登陆用户不使用，写起来就很麻烦，我们可以改写一下 helper 就可以了：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;cache_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;
      &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache_if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;user_signed_in?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小技巧 2：关联对象的自动更新
常使用对象 update_at 时间戳来作为 cache key，可以在关联对象上加上 touch 选项，自动更新关联对象时间戳，比如我们可以在更新或者删除文章评论的时候，自动个更新：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:touch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="5. 数据查询缓存"&gt;5. 数据查询缓存&lt;/h2&gt;
&lt;p&gt;通常来说 web 应用性能瓶颈都出现在 DB IO 上，做好数据查询缓存，减少数据库的查询次数，可以极大提高整体响应时间。
数据查询缓存分 2 种：
A. 同一个请求周期内的缓存
举一个显示文章列表的例子，输出文章标题和文章类别，对应代码如下&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controller&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# view&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@articles.each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
  &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会发生 10 条类似的 sql 查询：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rails 内置了 query cache（ &lt;a href="https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb&lt;/a&gt; ），在同一个请求周期内，如果没有 update/delete/insert 的操作，会对相同的 sql 查询进行缓存，如果文章类别都是相同的话，真正去查询数据库只会有 1 次。&lt;/p&gt;

&lt;p&gt;如果文章类别都不一样，就会出现 N+1 查询问题（常见的性能瓶颈），rails 推荐的解决方法是用 Eager Loading Associations ( &lt;a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations" rel="nofollow" target="_blank"&gt;http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations&lt;/a&gt; )&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询语句会变成&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;`categories`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;B. 跨请求周期的缓存
同请求周期缓存所带来性能优化是很有限的，很多时候我们需要用跨请求周期的缓存，将一些常用的数据（比如 User model）缓存，对于 active record 来说，利用统一的查询接口来 fetch cache，利用 callback 来 expire cache，就很容易实现，而且有一些现成的 gem 可以来用。&lt;/p&gt;

&lt;p&gt;比如说 identity_cache ( &lt;a href="https://github.com/Shopify/identity_cache" rel="nofollow" target="_blank"&gt;https://github.com/Shopify/identity_cache&lt;/a&gt; )&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;IdentityCache&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;IdentityCache&lt;/span&gt;
  &lt;span class="n"&gt;cached_belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 都会命中缓存&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 gem 的优点是代码实现简单，cache 设置灵活，也方便扩展，缺点是需要用不同的查询方法名（fetch），以及额外的关系定义。&lt;/p&gt;

&lt;p&gt;如果想在无数据缓存的应用无缝加入缓存功能，推荐&lt;a href="/hooopo" class="user-mention" title="@hooopo"&gt;&lt;i&gt;@&lt;/i&gt;hooopo&lt;/a&gt; 做的 second_level_cache ( &lt;a href="https://github.com/hooopo/second_level_cache" rel="nofollow" target="_blank"&gt;https://github.com/hooopo/second_level_cache&lt;/a&gt; ) 。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;acts_as_cached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:version&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:expires_in&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;week&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#还是使用find方法，就会命中缓存&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#无需额外用不一样的belongs_to定义&lt;/span&gt;
&lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现原理是扩展了 active record 底层 arel sql ast 处理（ &lt;a href="https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb" rel="nofollow" target="_blank"&gt;https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb&lt;/a&gt; ）
它的优点是无缝接入，缺点是扩展比较困难，对于只获取少量字段的查询无法缓存。&lt;/p&gt;
&lt;h2 id="6. 数据库缓存"&gt;6. 数据库缓存&lt;/h2&gt;
&lt;p&gt;编辑中&lt;/p&gt;

&lt;p&gt;这 6 种缓存，分布在客户端到服务器端不同的位置，所能够节约的时间也正好从多到少依次排列。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Mon, 19 May 2014 18:02:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/19389</link>
      <guid>https://ruby-china.org/topics/19389</guid>
    </item>
    <item>
      <title>升级 Ruby 2.1 以及 GC 调整</title>
      <description>&lt;p&gt;Ruby 从 1.8 =&amp;gt; 1.9 =&amp;gt; 2.0 =&amp;gt; 2.1 一直在 GC 上不断地改进，最近 2.1.1 刚刚发布，抽空将我们的应用从 2.0 升级了一下，记录一下相关改动。&lt;/p&gt;

&lt;p&gt;我们在服务器使用的是 rvm，首先更新一下 rvm：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rvm get &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;淘宝提供了 ruby 相关的镜像，可以更新一下源，后续安装会快很多：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s!cache.ruby-lang.org/pub/ruby!ruby.taobao.org/mirrors/ruby!'&lt;/span&gt; &lt;span class="nv"&gt;$rvm_path&lt;/span&gt;/config/db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 2.1.1：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rvm &lt;span class="nb"&gt;install &lt;/span&gt;2.1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建议使用 railsexpress 的性能优化补丁（其中包括已经合并到 ruby 2.2 method cache 的改进）：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rvm &lt;span class="nb"&gt;install &lt;/span&gt;2.1.1 &lt;span class="nt"&gt;--patch&lt;/span&gt; railsexpress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置 2.1.1 为默认版本：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rvm use 2.1.1 &lt;span class="nt"&gt;--default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OobGC 优化：
我们之前使用的 unicorn 自带的 OobGC，它会固定在每 N 次请求后，执行一次 GC。tmm1 写了另一个 OobGC: &lt;a href="https://github.com/tmm1/gctools" rel="nofollow" target="_blank"&gt;https://github.com/tmm1/gctools&lt;/a&gt; ，它利用 ruby 2.1 新的 gc 事件，使用 c 扩展来进行更智能的 OobGC，配置很简单，在 config.ru 里面加上：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'gctools/oobgc'&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OOB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UnicornMiddleware&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过他不支持请求阶段的 GC.disable，实际用我们的应用测下来和 unicorn 自带的 OobGC+GC.disable 相比，在普通压力测试下，平均响应时间稍慢 2~3%，服务器的 cpu 消耗会少 2% 左右。&lt;/p&gt;

&lt;p&gt;除了 OobGC 外，这个 gem 还提供了 GC 的日志输出，只要加上：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'gctools/logger'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在 stderr 看到具体的 GC 执行情况，用来判断 GC 参数调整是否合理十分有用。&lt;/p&gt;

&lt;p&gt;GC 参数调整：
和 Ruby 2.0 相比，2.1 多了一些分代 GC 的参数，这篇文章 &lt;a href="http://tmm1.net/ruby21-rgengc/" rel="nofollow" target="_blank"&gt;http://tmm1.net/ruby21-rgengc/&lt;/a&gt; 非常详细地介绍了各个参数的意义，还提供了 github 用的参数配置。配合 gctools/logger，我们最终调整的 GC 参数如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_HEAP_INIT_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;500000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_HEAP_FREE_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;700000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_HEAP_GROWTH_FACTOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.25
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_HEAP_GROWTH_MAX_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_MALLOC_LIMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80000000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_GC_OLDMALLOC_LIMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80000000 
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>quakewang</author>
      <pubDate>Fri, 28 Feb 2014 16:55:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/17575</link>
      <guid>https://ruby-china.org/topics/17575</guid>
    </item>
    <item>
      <title>周末活动介绍 Prawn 的 Slide</title>
      <description>&lt;p&gt;&lt;a href="http://quake.github.io/slides/prawn/index.html" rel="nofollow" target="_blank"&gt;http://quake.github.io/slides/prawn/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;简单介绍了一下 Prawn，顺带打了一下广告，主要目的是去参观薄荷网的小别墅，环境确实很赞！&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Mon, 25 Nov 2013 18:24:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/15785</link>
      <guid>https://ruby-china.org/topics/15785</guid>
    </item>
    <item>
      <title>[免费] 转让 RubyConfChina 门票一张</title>
      <description>&lt;p&gt;很杯具地...下周末公司搬家要去做苦力，帝都我来不了了...&lt;/p&gt;

&lt;p&gt;如果你是学生，请附上 github 账号，免费转让，随机抽一个。&lt;/p&gt;

&lt;p&gt;有意向的同学，请回复吧，本周末前截至。&lt;/p&gt;

&lt;p&gt;====抽奖分割线====
irb(main):004:0&amp;gt; %w(cassiuschen wcp1231 KuangyinWang shatle undoZen qinfanpeng shawzt GeassX).sample
=&amp;gt; "KuangyinWang"&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Thu, 17 Oct 2013 11:49:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/14810</link>
      <guid>https://ruby-china.org/topics/14810</guid>
    </item>
    <item>
      <title>[上海] 蝉游记 - 一大波职位正在向你靠近</title>
      <description>&lt;h2 id="写在前面的话"&gt;写在前面的话&lt;/h2&gt;
&lt;p&gt;以下招聘内容是我的合伙人写的，原文在： &lt;a href="http://firecacada.blog.163.com/blog/static/707437620138252491850/" rel="nofollow" target="_blank"&gt;http://firecacada.blog.163.com/blog/static/707437620138252491850/&lt;/a&gt; 虽然我们这次的一大波职位里面没有 Ruby 工程师，但是我还是想转发在 Ruby 社区试试看，如果碰巧看贴的你既熟悉 Ruby，又有丰富的 Android 开发经验也说不定，这可是豪大大的加分项。或者又碰巧你的好友有符合其他岗位的要求，不妨转给他看看。&lt;/p&gt;
&lt;h2 id="以下是正文"&gt;以下是正文&lt;/h2&gt;
&lt;p&gt;hi，我是蝉游记 lv2。你们可能认识我的小伙伴蝉游记 lv1，一款以华丽画卷为卖点的游记制作工具，有 &lt;a href="http://chanyouji.com" rel="nofollow" target="_blank" title="蝉游记网站"&gt;网站版&lt;/a&gt;与&lt;a href="https://itunes.apple.com/cn/app/id559653959?mt=8" rel="nofollow" target="_blank" title=""&gt;iPhone、iPad&lt;/a&gt;版本，在今年极客公园举办的创新产品大赛中，拿过“最佳体验奖”，App 多次进入 AppStore 首页编辑选荐。 &lt;/p&gt;

&lt;p&gt;现在，lv1 升级到了 lv2，蝉小队招聘启动，一大波职位正在向你靠近。 &lt;/p&gt;

&lt;p&gt;提前点名：Android 工程师，Android 产品经理，目的地运营，市场营销，UI 设计师，行政&lt;/p&gt;
&lt;h2 id="关于蝉游记lv2 "&gt;关于蝉游记 lv2 &lt;/h2&gt;
&lt;p&gt;虽然 lv1 以游记为主，但这并不是蝉小队的真实目的。游记只是我们收集结构化数据 (简称碎片) 的方式，然后再用碎片重新搭建结构化的旅行指南。或者我们换一个更容易理解的说法：wiki 形态的移动版《lonely planet》。 &lt;/p&gt;

&lt;p&gt;在这颗蓝色的孤独行星上，还从来没有出现过一款真正像样的，用 UGC 来搭建的结构化旅行指南。相比传统的路书，它更全面，更丰富，更适合在旅行途中使用；相比游记攻略，它有更高得多的查询效率。但这样的产品是不存在的——没错，目前还不存在——蝉小队想拿到这项成就。 &lt;/p&gt;

&lt;p&gt;我们都知道，LP 是旅行者的圣经。但就像 wikipedia 早已超越了大英百科全书，用不断滚动的海量游记碎片来组装的旅行指南，同时又借鉴了 LP 优秀的信息组织方式，将会为旅行者提供生动，多角度，更新维护飞快，图文并茂 (甚至包括视频) 的目的地信息服务。 &lt;/p&gt;

&lt;p&gt;蝉游记发布一年，产生游记数万篇，旅行相片数百万张，已经积累了足够的碎片。接下来，令人惊讶的旅行指南即将现身。 &lt;/p&gt;
&lt;h2 id="关于“旅行福利” "&gt;关于“旅行福利” &lt;/h2&gt;
&lt;p&gt;不旅行的人怎么做得好旅行产品？ &lt;/p&gt;

&lt;p&gt;为了身体力行，蝉小队实行全员旅行福利。每人每月有 1 天带薪旅行假期，1000 元旅行补贴，最多可累计 6 个月使用，不用作废。相当于每半年至少有一次长途旅行机会。 &lt;/p&gt;
&lt;h2 id="关于其他福利 "&gt;关于其他福利 &lt;/h2&gt;
&lt;p&gt;-每年 13 薪，全额缴纳社保公积金 
-午餐由家政阿姨烹饪，无须外出觅食，如果你坚持外出觅食则会获得 350 元的餐补 
-敞开供应零食饮料下午茶 (有神器“飞利浦空气炸锅”加持！) 并支持自选采购模式 
-春节放假 2 周 
-3 个单身妹子 (此处应有掌声) 
-没有周报，没有考核，没有 KPI，很少开会，但有严格的个人计划管理 
-养过叫“貂蝉”的灰龙猫，啃烟头自杀了；又养了叫“恶魔萌”的赛级拉布拉多犬，造成接近 8000 元破坏后，即将送人；我司 CEO 不死心，正在构思下一只团队宠物…… &lt;/p&gt;
&lt;h2 id="关于蝉小队"&gt;关于蝉小队&lt;/h2&gt;
&lt;p&gt;目前只有 6 个人……你没看错，只有 6 个人。&lt;/p&gt;

&lt;p&gt;联合创始人纯银 (郭子威)，之前在网易做了 5 年的内容和产品部门总监，最近为不再萌而烦恼。联合创始人 Quake(王益善)，12 年编程史的 Ruby 工程师，经常敲着敲着代码就自言自语说“有种想哭的感觉”。全能工程师凌晔，一人拿下了蝉游记的网站与 iOS 客户端。以及 UI 设计师菲仔，运营妹子莱拉与宝玉。&lt;/p&gt;

&lt;p&gt;顺带为莱拉妹子打个征男盆友小广告：&lt;a href="http://chanyouji.com/trips/46202" rel="nofollow" target="_blank"&gt;http://chanyouji.com/trips/46202&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;还有一只损坏了 2 块硬盘，1 台电脑，2 组沙发，1 个茶几，一整排窗帘，并在地板上刨出 3 个大坑的恶魔般的拉布拉多犬。&lt;/p&gt;
&lt;h2 id="温情告知 "&gt;温情告知 &lt;/h2&gt;
&lt;p&gt;-在上海浦东的民宅办公，不喜欢太正经的氛围 
-闲聊时的口味略重，格调略低，腐女们略嚣张 
-蝉小队政治立场偏右 &lt;/p&gt;
&lt;h2 id="招聘职位 "&gt;招聘职位 &lt;/h2&gt;&lt;h2 id="▎Android工程师 "&gt;▎Android 工程师 &lt;/h2&gt;
&lt;p&gt;通常情况下同一款应用的 iOS 版本会比 Android 版更流畅，更精致，但我们想试试，能不能用更强大的蝉游记 Android 版来打败自家的 iOS 版本。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-1 年以上 Android 平台开发经验，有作为主力工程师完整开发过一个以上 Android 应用的经验 
-精通 Android UI 布局，精通不同分辨率适配，熟悉多线程、SQLite 数据库等操作 
-精通 HTTP 通讯协议，有 Restful API 使用经验，熟悉 JSON 数据格式 
-熟悉 Android OS 系统体系结构、framework 以及底层库等，熟悉 Android 的各种开源组件和 Android 界面设计规范 
-有 Google Play，360，豌豆夹等国内外主要市场的上架经验
-熟悉 git，了解 gitflow &lt;/p&gt;

&lt;p&gt;加分项： 
-有图片、音频、视频、地图类应用开发经验
-有 Unix 环境经验，日常使用 Unix 环境开发
-有开源的 Android 项目
-会使用 PhotoShop 或者其他图片处理软件进行简单操作
-熟悉 Ruby(豪大大地加分) 或者 Python 语言 
-果黑...&lt;/p&gt;

&lt;p&gt;应聘须知： 
请提供你负责的上架 App 名称和下载地址，并请预先体验蝉游记 iOS 版本，总结其中的功能或者交互在 Android 上实现的难点，或提出在 Android 上有更好的实现方式。如有 Github 账户/个人微博/博客地址也请一并告知。&lt;/p&gt;
&lt;h2 id="▎Android产品经理 "&gt;▎Android 产品经理 &lt;/h2&gt;
&lt;p&gt;蝉游记的 iOS 版口碑不错，我们希望能在这基础上做出更好的 Android 版，争取拿个奖啥的，而不仅仅是冲个量。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-2 年以上产品资历 
-负责过一款以上的上架 Android 应用设计 
-交互设计挥洒自如，在蝉小队，PM 就是交互 
-承担繁琐的测试工作（iOS 版的测试用例有 400 多条），在蝉小队，PM 就是 QA &lt;/p&gt;

&lt;p&gt;加分项： 
-Android 生态与 iOS 生态双修 
-有过一次以上长途自由行的经历 &lt;/p&gt;

&lt;p&gt;应聘须知： 
请告知你的 Android 手机型号，介绍你负责的 Android 应用，提名 10 款你眼中的最佳 Android 应用，并回答你对蝉游记进行数据结构化的手段与效果的看法。个人微博/博客地址也请一并告知。 &lt;/p&gt;

&lt;p&gt;又及，有意转型产品经理的交互设计师也可以试试这个职位。 &lt;/p&gt;
&lt;h2 id="▎目的地运营 "&gt;▎目的地运营 &lt;/h2&gt;
&lt;p&gt;负责结构化旅行指南的制作，游记审核，官微运营与用户关系。希望你是一本活的路书，或者接近这个效果。即便不是，在这个岗位上工作半年后，你就是一本活的路书。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-有过三次以上长途自由行的经历，长时间不旅行有可能憋死 
-习惯在出行前做足功课，制定超详细的日程计划 
-好笔头，爱写字，有长期经营的微博、博客或空间 
-英文不错，浏览 TripAdvisor.com 的点评内容无障碍 &lt;/p&gt;

&lt;p&gt;加分项： 
-有过出国旅行的经历 (去得越多越远就越赞) 
-写过不少游记，若有攻略更佳
-整理控 
-App 大玩家，追逐一切新潮应用
-很多朋友评价你“外向活泼”或“亲和力强”或“耐心细致” 
-在社交网络有一大群粉丝 &lt;/p&gt;

&lt;p&gt;应聘须知： 
请介绍过往的旅行经历，未来最想去的 3 个目的地，提供一份由你亲手制定的旅行日程计划文档，以及个人微博/博客/空间地址。 &lt;/p&gt;

&lt;p&gt;又及，运营人员将分两到三批招聘。如果你希望明年初再应聘，也可以先发一份简历在我这里存档，届时会主动跟你联系。 &lt;/p&gt;
&lt;h2 id="▎市场营销 "&gt;▎市场营销 &lt;/h2&gt;
&lt;p&gt;接下来的营销重心主要是拉升 App 下载量，也包括提升用户活跃度，外部业务合作，以及从无到有地培植旅行指南的品牌。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-1 年以上 App 推广资历 
-对安卓渠道了如指掌，有自己的人脉关系
-擅长推广物料设计 (当然是你构思卖点与文案，再协调设计师来设计) 
-长期保持严谨的数据分析习惯 &lt;/p&gt;

&lt;p&gt;加分项： 
-丰富的广告投放经验 
-有过一次以上长途自由行的经历 &lt;/p&gt;

&lt;p&gt;应聘须知： 
请介绍过去值得一提的推广业绩，提供你负责的推广物料样例，活动样例，并告知个人微博/博客地址。 &lt;/p&gt;

&lt;p&gt;又及，我们会根据应聘者的情况来灵活确定这个职位的权责与 Title。 &lt;/p&gt;
&lt;h2 id="▎行政"&gt;▎行政&lt;/h2&gt;
&lt;p&gt;创业团队都需要这样一个妹子，负责日常行政事务，帮新加入的员工办理社保，记录报销和时序账，偶尔还要跑一下工商税务啥的。但蝉小队这些方面的事儿又不多，所以我还希望她能负责一定的内容运营工作，甚至未来转职运营也说不定。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-85-90 年的妹子，2 年以上行政/人事/会计相关工作经验 
-有会计从业资格证
-好笔头，爱写字，有长期经营的微博、博客或空间 
-严格来说，我们这次需要的不是一个行政人事财务老手，而是除了能做好那些本职之外，还对运营工作兴致勃勃并且力能胜任的人 &lt;/p&gt;

&lt;p&gt;加分项：
-美少女
-有过一次以上长途自由行的经历 &lt;/p&gt;

&lt;p&gt;应聘须知： 
比对着“目的地运营”职位的应聘须知，提供力所能及的材料，并告知个人微博/博客地址。&lt;/p&gt;
&lt;h2 id="▎UI设计师（存档） "&gt;▎UI 设计师（存档） &lt;/h2&gt;
&lt;p&gt;此次招聘并不含 UI 设计岗位，但蝉小队在半年内还会增加另一位 UI 设计师。所以有意在春节前后换工作的朋友，不妨先发一份简历在我这里存档，届时会主动跟你联系。事实上，我们希望能对一整个蝉游记进行重新设计，而不是在旧框架上修修补补。 &lt;/p&gt;

&lt;p&gt;基本要求： 
-3 年以上 UI 设计资历，其中 App 设计不少于 1 年 
-有独立制定产品视觉基调的成功案例 
-手绘功底不俗，擅长 icon 设计 
-App 大玩家，追逐一切新潮应用 &lt;/p&gt;

&lt;p&gt;加分项： 
-Android 生态与 iOS 生态双修 
-对产品交互设计，甚至是功能策划的想法多多，希望参与产品的更多环节 
-摄影高手 
-有过一次以上长途自由行的经历 &lt;/p&gt;

&lt;p&gt;应聘须知： 
请提供包括 icon 在内的视觉作品集，并告知个人微博/博客地址。 &lt;/p&gt;

&lt;p&gt;应聘方式
请发邮件到 firecicada@gmail.com，邮件标题注明应聘职位。如符合条件，会在 5 个工作日内收到回复。&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Wed, 25 Sep 2013 15:11:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/14380</link>
      <guid>https://ruby-china.org/topics/14380</guid>
    </item>
    <item>
      <title>蝉游记网站的部署 Nginx,Unicorn,Capistrano,OOB,Graceful Restart</title>
      <description>&lt;p&gt;蝉游记（ &lt;a href="http://chanyouji.com" rel="nofollow" target="_blank"&gt;http://chanyouji.com&lt;/a&gt; ）网站之前用 Nginx+Passenger+ 自制 script 来部署，随着用户增多，移动 app 的 api 调用增加，服务器增多和无缝部署重启的需求，转移到了 Nginx+Unicorn+Capistrano，写篇博客记录一下各种细节和需要注意的地方。&lt;/p&gt;

&lt;p&gt;Nginx 的配置&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;gzip&lt;/span&gt;  &lt;span class="n"&gt;on&lt;/span&gt;;
&lt;span class="c"&gt;#开启gzip，同时对于api请求的json格式也开启gzip
&lt;/span&gt;&lt;span class="n"&gt;gzip_types&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;json&lt;/span&gt;;

&lt;span class="c"&gt;#每台机器都运行nginx+unicorn，本机用domain socket，方便切换
&lt;/span&gt;&lt;span class="n"&gt;upstream&lt;/span&gt; &lt;span class="n"&gt;ruby_backend&lt;/span&gt; {
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;unix&lt;/span&gt;:/&lt;span class="n"&gt;tmp&lt;/span&gt;/&lt;span class="n"&gt;unicorn&lt;/span&gt;.&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="n"&gt;fail_timeout&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;4&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;.&lt;span class="m"&gt;34&lt;/span&gt;:&lt;span class="m"&gt;4096&lt;/span&gt; &lt;span class="n"&gt;fail_timeout&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;4&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;:&lt;span class="m"&gt;4096&lt;/span&gt; &lt;span class="n"&gt;fail_timeout&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;;
}

&lt;span class="c"&gt;#用try_files方式和proxy执行rails动态请求
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt;       &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="n"&gt;chanyouji&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;
    &lt;span class="n"&gt;root&lt;/span&gt;         /&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;youji_deploy&lt;/span&gt;/&lt;span class="n"&gt;current&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;

    &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; @&lt;span class="n"&gt;user1&lt;/span&gt;;

    &lt;span class="n"&gt;location&lt;/span&gt; @&lt;span class="n"&gt;user2&lt;/span&gt; {
      &lt;span class="n"&gt;proxy_redirect&lt;/span&gt;     &lt;span class="n"&gt;off&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;   &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;   &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;   &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Server&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;   &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Real&lt;/span&gt;-&lt;span class="n"&gt;IP&lt;/span&gt;        $&lt;span class="n"&gt;remote_addr&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;   &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt;  $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_buffering&lt;/span&gt;    &lt;span class="n"&gt;on&lt;/span&gt;;
      &lt;span class="n"&gt;proxy_pass&lt;/span&gt;         &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;ruby_backend&lt;/span&gt;;
   }
}

&lt;span class="c"&gt;#用不同的域名提供静态资源服务，减少主域名带来的cookie请求和方便做cdn源
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt;       &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="n"&gt;cdn&lt;/span&gt;.&lt;span class="n"&gt;chanyouji&lt;/span&gt;.&lt;span class="n"&gt;cn&lt;/span&gt; &lt;span class="n"&gt;cdnsource&lt;/span&gt;.&lt;span class="n"&gt;chanyouji&lt;/span&gt;.&lt;span class="n"&gt;cn&lt;/span&gt;;
    &lt;span class="n"&gt;root&lt;/span&gt;         /&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;youji_deploy&lt;/span&gt;/&lt;span class="n"&gt;current&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;

    &lt;span class="n"&gt;location&lt;/span&gt; ~ ^/(&lt;span class="n"&gt;assets&lt;/span&gt;)/  {
      &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;youji_deploy&lt;/span&gt;/&lt;span class="n"&gt;current&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;
      &lt;span class="n"&gt;gzip_static&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;; &lt;span class="c"&gt;# to serve pre-gzipped version
&lt;/span&gt;      &lt;span class="n"&gt;expires&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;;
      &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Cache&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;unicorn.rb 的配置&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;worker_processes&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="n"&gt;app_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../.."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;working_directory&lt;/span&gt; &lt;span class="n"&gt;app_root&lt;/span&gt;

&lt;span class="c1"&gt;# Listen on fs socket for better performance&lt;/span&gt;
&lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/unicorn.sock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:backlog&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tcp_nopush&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# Nuke workers after 30 seconds instead of 60 seconds (the default)&lt;/span&gt;
&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="c1"&gt;# App PID&lt;/span&gt;
&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/pids/unicorn.pid"&lt;/span&gt;

&lt;span class="c1"&gt;# By default, the Unicorn logger will write to stderr.&lt;/span&gt;
&lt;span class="c1"&gt;# Additionally, some applications/frameworks log to stderr or stdout,&lt;/span&gt;
&lt;span class="c1"&gt;# so prevent them from going to /dev/null when daemonized here:&lt;/span&gt;
&lt;span class="n"&gt;stderr_path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/log/unicorn.stderr.log"&lt;/span&gt;
&lt;span class="n"&gt;stdout_path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/log/unicorn.stdout.log"&lt;/span&gt;

&lt;span class="c1"&gt;# To save some memory and improve performance&lt;/span&gt;
&lt;span class="n"&gt;preload_app&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:copy_on_write_friendly&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
  &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_on_write_friendly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Force the bundler gemfile environment variable to&lt;/span&gt;
&lt;span class="c1"&gt;# reference the Сapistrano "current" symlink&lt;/span&gt;
&lt;span class="n"&gt;before_exec&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"BUNDLE_GEMFILE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Gemfile'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;before_fork&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# 参考 http://unicorn.bogomips.org/SIGNALS.html&lt;/span&gt;
  &lt;span class="c1"&gt;# 使用USR2信号，以及在进程完成后用QUIT信号来实现无缝重启&lt;/span&gt;
  &lt;span class="n"&gt;old_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_root&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'/tmp/pids/unicorn.pid.oldbin'&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old_pid&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"QUIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ENOENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ESRCH&lt;/span&gt;
      &lt;span class="c1"&gt;# someone else did our job for us&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# the following is highly recomended for Rails + "preload_app true"&lt;/span&gt;
  &lt;span class="c1"&gt;# as there's no need for the master process to hold a connection&lt;/span&gt;
  &lt;span class="k"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after_fork&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# 禁止GC，配合后续的OOB，来减少请求的执行时间&lt;/span&gt;
  &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt;
  &lt;span class="c1"&gt;# the following is *required* for Rails + "preload_app true",&lt;/span&gt;
  &lt;span class="k"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establish_connection&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GC OOB&lt;/p&gt;

&lt;p&gt;这篇 newrelic 的文章解释很清楚： &lt;a href="http://blog.newrelic.com/2013/05/28/unicorn-rawk-kick-gc-out-of-the-band/" rel="nofollow" target="_blank"&gt;http://blog.newrelic.com/2013/05/28/unicorn-rawk-kick-gc-out-of-the-band/&lt;/a&gt;
就是将 GC 延迟到用户请求完成以后，这样就会缩短响应时间，配合现成的 gem unicorn-worker-killer 也不用担心内存爆掉。&lt;/p&gt;

&lt;p&gt;在 config.ru 里面配置：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'unicorn/oob_gc'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'unicorn/worker_killer'&lt;/span&gt;
&lt;span class="c1"&gt;#每10次请求，才执行一次GC&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Unicorn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OobGC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="c1"&gt;#设定最大请求次数后自杀，避免禁止GC带来的内存泄漏（3072～4096之间随机，避免同时多个进程同时自杀，可以和下面的设定任选）&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Unicorn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WorkerKiller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MaxRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3072&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;
&lt;span class="c1"&gt;#设定达到最大内存后自杀，避免禁止GC带来的内存泄漏（192～256MB之间随机，避免同时多个进程同时自杀）&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Unicorn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WorkerKiller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Oom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../config/environment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Youji&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Capistrano 部署脚本&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:unicorn_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/unicorn.rb"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:unicorn_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/pids/unicorn.pid"&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:deploy&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:roles&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:except&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:no_release&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"cd &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; RAILS_ENV=production bundle exec unicorn_rails -c &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;unicorn_config&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -D"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:roles&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:except&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:no_release&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"if [ -f &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;unicorn_pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ]; then kill -QUIT `cat &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;unicorn_pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`; fi"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:restart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:roles&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:except&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:no_release&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# 用USR2信号来实现无缝部署重启&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"if [ -f &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;unicorn_pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ]; then kill -s USR2 `cat &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;unicorn_pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`; fi"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成这些改进以后，部署蝉游记的新版本就只用输入 cap production deploy，然后就可以喝茶去了，也不用担心用户在重启动的时候会有短期卡死的问题 :)&lt;/p&gt;

&lt;p&gt;补 2 张图：
new relic 的监控图，和启用 OOB 之前相比，平均响应时间从 100ms 左右下降到了 90ms 左右：
&lt;img src="//l.ruby-china.com/photo/ef40a120135a15e85dbb511304ab58c5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;服务器的内存和 CPU 使用：
&lt;img src="//l.ruby-china.com/photo/e1c0daea1a59dbc4988862a149c04df9.png" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Thu, 27 Jun 2013 12:15:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/12033</link>
      <guid>https://ruby-china.org/topics/12033</guid>
    </item>
    <item>
      <title>都来聊聊人生污点吧</title>
      <description>&lt;p&gt;在生产数据库服务器上执行 delete 语句的时候，很开心地少打了一个 where 条件，导致整个表被清空。 &lt;/p&gt;

&lt;p&gt;防火墙配置限制 ip 访问，复制粘贴的时候出错，导致数据库服务器断开了所有的链接。&lt;/p&gt;

&lt;p&gt;和同事一起维护，他用 root 账户清理一些文件，以为在/tmp 目录下面，执行了 rm . -rf，看到屏幕飞快地刷，我忽然有一种很不好的感觉，赶紧喊 ctrl+c，然后过 1 秒等他明白过来狂按的时候，才发现服务器的 IO 性能太好有时候也是一种错误...&lt;/p&gt;

&lt;p&gt;本人亲历...楼下继续...&lt;/p&gt;</description>
      <author>quakewang</author>
      <pubDate>Sun, 06 Jan 2013 10:54:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/7801</link>
      <guid>https://ruby-china.org/topics/7801</guid>
    </item>
  </channel>
</rss>
