<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>xguox (XguoX)</title>
    <link>https://ruby-china.org/xguox</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Ruby 的并发, 进程, 线程, GIL, EventMachine, Celluloid</title>
      <description>&lt;p&gt;&lt;a href="https://twitter.com/RubyInside" rel="nofollow" target="_blank" title=""&gt;RubyInside&lt;/a&gt; 最近推了一篇文章&lt;a href="https://engineering.universe.com/introduction-to-concurrency-models-with-ruby-part-i-550d0dbb970" rel="nofollow" target="_blank" title=""&gt;关于 Ruby 的并发&lt;/a&gt;, 加一点，删一点半翻译理解了一下，讲的都是基础没深入涉及原理，Ruby China 可以搜到过去相关的讨论也很多，更深层的也不少&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;关于并发与并行，前不久刚好真实发生。同事一行人去 Family Mart 买午餐，拿回来公司只有一个微波炉加热，在 Family Mart 有两个微波炉可以加热。也就是说，我们一行人一起去买午餐这是一个并发的程序，然后在 Family Mart 可以并行加热，但是，如果拿回公司的话，因为只有一个微波炉 (单核), 所以是只能一个接一个顺序执行。&lt;/p&gt;
&lt;h5 id="Processes"&gt;Processes&lt;/h5&gt;&lt;h6 id="串行执行"&gt;串行执行&lt;/h6&gt;
&lt;p&gt;给一个 range, 转成 array 以后获取某个特定数字的 index&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;100_000_000&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;99_999_999&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;range&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="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;sequential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="mi"&gt;99999999&lt;/span&gt;
&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;sequential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;  &lt;span class="mf"&gt;4.04&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;0.56&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt; &lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="mf"&gt;4.667&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行这段代码大约耗时 5s, 利用了 99% 的 CPU(单核).&lt;/p&gt;
&lt;h6 id="并行执行"&gt;并行执行&lt;/h6&gt;
&lt;p&gt;通过分割 range 以及 Ruby 标准库中的 &lt;code&gt;fork&lt;/code&gt; 方法，我们可以使用多个进程来执行上面的代码。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;range1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;50_000_000&lt;/span&gt;
&lt;span class="n"&gt;range2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50_000_000&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;100_000_000&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;99_999_999&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Parent &lt;/span&gt;&lt;span class="si"&gt;#{&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;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;fork&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Child1 &lt;/span&gt;&lt;span class="si"&gt;#{&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;pid&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;range1&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="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;fork&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Child2 &lt;/span&gt;&lt;span class="si"&gt;#{&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;pid&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;range2&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="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&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="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Parent&lt;/span&gt; &lt;span class="mi"&gt;5086&lt;/span&gt;
&lt;span class="no"&gt;Child2&lt;/span&gt; &lt;span class="mi"&gt;5100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;49999999&lt;/span&gt;
&lt;span class="no"&gt;Child1&lt;/span&gt; &lt;span class="mi"&gt;5099&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;  &lt;span class="mf"&gt;3.73&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;0.43&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt; &lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="mf"&gt;2.162&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在多核 CPU 下，因为是把 range 对半拆开，所以，处理时间也快了。(数字有时候会有一些差距). &lt;code&gt;Process.wait&lt;/code&gt; 是等待所有子进程完成的意思。&lt;/p&gt;

&lt;p&gt;因为 GIL(global interpreter lock) 的存在，Ruby MRI 要利用 CPU 的核数唯一方法就是多进程。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bogomips.org/unicorn/" rel="nofollow" target="_blank" title=""&gt;Unicorn&lt;/a&gt; 就是通过 fork Rails 主进程衍生出多个 workers 来处理 HTTP 请求的。&lt;/p&gt;

&lt;p&gt;这种多进程的方式最大优点是充分的利用多核 CPU, 且进程间不共享内存，因此 debug 也会容易一些，但是因为不共享内存，也就意味着内存消耗会变大。不过从 Ruby 2.0 开始使用系统的 COW(Copy On Write) 功能可以让 fork 的进程共享内存数据，只有在数据改变时候才复制数据。&lt;/p&gt;
&lt;h5 id="Threads"&gt;Threads&lt;/h5&gt;
&lt;p&gt;Ruby 从 1.9 开始，线程的实现改为系统的 Native 线程，线程也由操作系统来完成调度。但是由于 GIL 的存在，同一时间一个 Ruby 进程只会有一个线程获取到 GIL 在跑。GIL 的存在意义就是线程安全，防止发生竞争条件，比起在数据结构上实现线程安全，GIL 的实现方式更为容易一些。&lt;/p&gt;

&lt;p&gt;然而，其实，GIL 并不能完全阻止竞争条件的发生 = . = &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race Condition&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# threads.rb&lt;/span&gt;
&lt;span class="vi"&gt;@executed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ensure_executed&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@executed&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"executing!"&lt;/span&gt;
    &lt;span class="vi"&gt;@executed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;threads&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="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="n"&gt;ensure_executed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;threads&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;span class="c1"&gt;# 主线程等待子线程执行完毕再继续往后面跑, 比如在后面 p 'done'.&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="n"&gt;executing!&lt;/span&gt;
&lt;span class="n"&gt;executing!&lt;/span&gt;
&lt;span class="n"&gt;executing!&lt;/span&gt;
&lt;span class="n"&gt;executing!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为所有的线程共享一个 &lt;code&gt;@executed&lt;/code&gt; 变量，并且读 (&lt;code&gt;unless @executed&lt;/code&gt;) 写 (&lt;code&gt;@executed = true&lt;/code&gt;) 操作又不是原子级的，也就是说当在某个线程中读这个 &lt;code&gt;@executed&lt;/code&gt; 的值的时候，可能在别的线程已经把 &lt;code&gt;@executed&lt;/code&gt; 给改写了。 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GIL and Blocking I/O&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GIL 不能让多个线程同时跑的话那还要多线程来干啥？其实还是有其高端的地方的。当线程遇到&lt;strong&gt;阻塞 I/O&lt;/strong&gt;时就会释放 GIL 给到其他线程，比如 HTTP 请求，数据库查询，磁盘读写，甚至 &lt;code&gt;sleep&lt;/code&gt;  .&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sleep.rb&lt;/span&gt;
&lt;span class="n"&gt;threads&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="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="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="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="nb"&gt;sleep&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;end&lt;/span&gt;
&lt;span class="n"&gt;threads&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;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;  &lt;span class="mf"&gt;0.09&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="mf"&gt;1.146&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;十个线程执行 &lt;code&gt;sleep&lt;/code&gt; 并不需要执行 10s. 线程执行到 &lt;code&gt;sleep&lt;/code&gt; 执行权就会递交出去。相比起使用进程，线程更轻量一些，你甚至可以跑好几千个线程，处理阻塞 I/O 的操作也非常有用。但是，得要小心 &lt;strong&gt;race-conditions&lt;/strong&gt;. 如果用了互斥锁 (Mutex) 来避免的话，又得注意死锁之类的出现。此外，线程之间的切换也是会有消耗的，所以，太多线程的话，会把时间都花在切换线程上了。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/puma/puma" rel="nofollow" target="_blank" title=""&gt;Puma&lt;/a&gt; 允许在每个进程中使用多线程，每个进程都有各自的线程池。大部分时候不会遇到上面说的竞争问题，因为每个 HTTP 请求都是在不同的线程处理。&lt;/p&gt;
&lt;h5 id="EventMachine"&gt;EventMachine&lt;/h5&gt;
&lt;p&gt;EventMachine(EM) 是一个基于 Reactor 设计模式提供事件驱动 (event-driven) I/O 的 gem.
使用 EventMachine 的一个好处就是当处理大量 IO 操作的时候，不需要手工处理多线程，EM 可以在一个线程里边处理多个 HTTP 请求。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# em.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'eventmachine'&lt;/span&gt;

&lt;span class="no"&gt;EM&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;do&lt;/span&gt;
  &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_timer&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;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'sleeping...'&lt;/span&gt;
    &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sleep 1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"woke up!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'continuing...'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_timer&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="n"&gt;sleeping&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;continuing&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;woke&lt;/span&gt; &lt;span class="n"&gt;up!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;EM.system&lt;/code&gt; 模拟了 I/O 操作，并传入一个 block 作为回调，在等待回调期间可以继续响应别的操作 (事件). 但是复杂的系统因为要处理成功与失败的回调，而且可能回调内部嵌套着其他事件和回调，因此，很容易就会陷入 &lt;strong&gt;Callback Hell&lt;/strong&gt;&lt;/p&gt;
&lt;h5 id="Fiber"&gt;Fiber&lt;/h5&gt;
&lt;p&gt;用的巨少，语法掌握了又忘，语法掌握了又忘 = . =&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ruby-doc.org/core-2.4.1/Fiber.html" rel="nofollow" target="_blank" title=""&gt;Fiber&lt;/a&gt; 是 Ruby 1.9 开始新增的轻量级运行单元。类似线程的暂停，恢复执行代码，最大的区别在于，线程是由于操作系统调度执行，而 Fiber 是由程序员自己处理调度。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# fiber.rb&lt;/span&gt;
&lt;span class="n"&gt;fiber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# 3. Fiber.yield 交出执行权, 并返回 1 (在这里)&lt;/span&gt;
  &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="c1"&gt;# 5. 执行完毕, 返回 2&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;# 1. fiber 创建以后不会执行里边的代码&lt;/span&gt;

&lt;span class="c1"&gt;# 2. 调用 resume 才会执行&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Go on here...&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;

&lt;span class="c1"&gt;# 5. fiber 已挂, 报错&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;

&lt;span class="err"&gt;➜&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;dead&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt; &lt;span class="n"&gt;called&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FiberError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiber 结合 EventMachine 可以避免 &lt;strong&gt;Callback Hell&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&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;do&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&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="s1"&gt;'https://google.ca/'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;       
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Google is down"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://google.ca/search?q=universe.com'&lt;/span&gt;
    &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&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;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;
    &lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errback&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;about&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callback&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="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 Fiber 重写&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&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;do&lt;/span&gt;
  &lt;span class="no"&gt;Fiber&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://www.google.com/'&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
      &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://google.ca/search?q=universe.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="c1"&gt;# ... &lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; 
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Google is down"&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;resume&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;http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;current_fiber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&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;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;    
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errback&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;    
  &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果在 Fiber 中执行 I/O 操作，整个线程将会被这个 fiber 阻塞住，终归还是 GIL 的原因，其实并没有真正的解决并发的问题，而且 Fiber 的语法估计我后天又忘了。&lt;/p&gt;
&lt;h5 id="Celluloid"&gt;Celluloid&lt;/h5&gt;
&lt;p&gt;Celluloid 借鉴了 Erlang 给 Ruby 带来了相似的 Actor 模型。每个 &lt;code&gt;include&lt;/code&gt; 了 &lt;code&gt;Celluloid&lt;/code&gt; 的类都变成了一个拥有自己的执行线程的 Actor.&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;Sheen&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Celluloid&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&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="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&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;report&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;00&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;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;charlie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sheen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="s2"&gt;"Charlie Sheen"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Celluloid::Proxy::Cell(Sheen:0x3ffd8ac62b50) @name="Charlie Sheen"&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;010&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="n"&gt;charlie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_status&lt;/span&gt; &lt;span class="s2"&gt;"winning!"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"winning!"&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;011&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="n"&gt;charlie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Charlie Sheen is winning!"&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;012&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="n"&gt;charlie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_status&lt;/span&gt; &lt;span class="s2"&gt;"asynchronously winning!"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Celluloid::Proxy::Async(Sheen)&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;013&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="n"&gt;charlie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Charlie Sheen is asynchronously winning!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Celluloid::Proxy::Async&lt;/code&gt; 对象会截获方法的调用，然后保存到 Actor 并发对象的调用队列中，程序不必等待响应就可以往下执行 (异步). 每个并发对象都有一个自己调用队列，并且按顺序地一个接一个执行里边的方法调用。&lt;/p&gt;

&lt;p&gt;Actor 之间通过发送消息来交流，而与 Erlang 的 Actor 模型最大的区别就在于 &lt;strong&gt;Erlang 的变量是不可变的&lt;/strong&gt;, 而 Ruby 没有这个限制，所以，消息 (对象) 在传递的过程就可能会被修改了，除非 &lt;code&gt;freeze&lt;/code&gt;?&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;a href="https://engineering.universe.com/introduction-to-concurrency-models-with-ruby-part-i-550d0dbb970" rel="nofollow" target="_blank" title=""&gt;https://engineering.universe.com/introduction-to-concurrency-models-with-ruby-part-i-550d0dbb970&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil" rel="nofollow" target="_blank" title=""&gt;https://www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil&lt;/a&gt;&lt;br&gt;
&lt;a href="http://merbist.com/2011/02/22/concurrency-in-ruby-explained/" rel="nofollow" target="_blank" title=""&gt;http://merbist.com/2011/02/22/concurrency-in-ruby-explained/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.slideshare.net/mperham/actors-and-threads" rel="nofollow" target="_blank" title=""&gt;https://www.slideshare.net/mperham/actors-and-threads&lt;/a&gt;&lt;br&gt;
&lt;a href="http://practicingruby.com/articles/gentle-intro-to-actor-based-concurrency" rel="nofollow" target="_blank" title=""&gt;http://practicingruby.com/articles/gentle-intro-to-actor-based-concurrency&lt;/a&gt;  &lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;题外话有没一起玩 Erlang 的小伙伴，贡献几篇笔记&lt;/p&gt;

&lt;p&gt;&lt;a href="http://xguox.me/this-week-with-erlang.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 01&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-02.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 02 - Module 模块&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-03.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 03 - Function 函数&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-04.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 04 - Recursion 递归&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-05.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 05 - Records &amp;amp; Maps 记录与键值对&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-06.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 06 - Exceptions 异常&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/erlang-weekly-note-07.html" rel="nofollow" target="_blank" title=""&gt;Erlang weekly note 07 - Processes 进程&lt;/a&gt;  &lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Thu, 31 Aug 2017 10:33:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/33987</link>
      <guid>https://ruby-china.org/topics/33987</guid>
    </item>
    <item>
      <title>ElasticSearch 的分数 (_score) 是怎么计算得出 (2.X &amp; 5.X)</title>
      <description>&lt;p&gt;上次写了关于 &lt;a href="http://xguox.me/elasticsearch-custom-analyzer.html" rel="nofollow" target="_blank" title=""&gt;Elasticsearch 如何分词索引&lt;/a&gt;, 接着继续写 Elasticsearch 怎么计算搜索结果的得分 (_score).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elasticsearch 默认是按照文档与查询的相关度 (匹配度) 的得分倒序返回结果的。得分 (_score) 就越大，表示相关性越高。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;所以，相关度是啥？分数又是怎么计算出来的？(全文检索和结构化的 SQL 查询不太一样，虽然看起来结果比较'飘忽', 但也是可以追根问底的)&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在 Elasticsearch 中，标准的算法是  &lt;strong&gt;Term Frequency/Inverse Document Frequency, 简写为 TF/IDF&lt;/strong&gt;, (刚刚发布的 5.0 版本，改为了据说更先进的 BM25 算法)&lt;/p&gt;
&lt;h3 id="Term Frequency"&gt;Term Frequency&lt;/h3&gt;
&lt;p&gt;某单个关键词 (term) 在某文档的某字段中出现的频率次数，显然，出现频率越高意味着该文档与搜索的相关度也越高&lt;/p&gt;

&lt;p&gt;具体计算公式是 &lt;strong&gt;tf(q in d) = sqrt(termFreq)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;另外，索引的时候可以做一些设置，"index_options": "docs" 的情况下，只考虑 term 是否出现 (命中), 不考虑出现的次数。&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/my_index&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"index_options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docs"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Inverse document frequency"&gt;Inverse document frequency&lt;/h3&gt;
&lt;p&gt;某个关键词 (term) 在索引 (单个分片) 之中出现的频次。出现频次越高，这个词的相关度越低。相对的，当某个关键词 (term) 在一大票的文档下面都有出现，那么这个词在计算得分时候所占的比重就要比那些只在少部分文档出现的词所占的得分比重要低。说的那么长一句话，用人话来描述就是 &lt;strong&gt;"物以稀为贵"&lt;/strong&gt;, 比如，'的', '得', 'the' 这些一般在一些文档中出现的频次都是非常高的，因此，这些词占的得分比重远比特殊一些的词 (如'Solr', 'Docker', '哈苏') 占比要低，&lt;/p&gt;

&lt;p&gt;具体计算公式是 &lt;strong&gt;idf = 1 + ln(maxDocs/(docFreq + 1))&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="Field-length Norm"&gt;Field-length Norm&lt;/h3&gt;
&lt;p&gt;字段长度，这个字段长度越短，那么字段里的每个词的相关度也就越大。某个关键词 (term) 在一个短的句子出现，其得分比重比在一个长句子中出现要来的高。&lt;/p&gt;

&lt;p&gt;具体计算公式是 &lt;strong&gt;norm = 1/sqrt(numFieldTerms)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;默认每个 analyzed 的 string 都有一个 norm 值，用来存储该字段的长度，&lt;/p&gt;

&lt;p&gt;用 "norms": { "enabled": false } 关闭以后，评分时，不管文档的该字段长短如何，得分都一样。&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/my_index&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"norms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="最后的得分是三者的乘积 tf * idf * norm"&gt;最后的得分是三者的乘积 tf * idf * norm&lt;/h5&gt;
&lt;p&gt;以上描述的是最原始的针对&lt;strong&gt;单个关键字 (term)&lt;/strong&gt;的搜索。如果是有&lt;strong&gt;多个搜索关键词 (terms)&lt;/strong&gt;的时候，还要用到的 &lt;strong&gt;Vector Space Model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果查询复杂些，或者用到一些修改了分数的查询，或者索引时候修改了字段的权重，比如 &lt;strong&gt;function_score&lt;/strong&gt; 之类的，计算方式也就又更复杂一些。&lt;/p&gt;
&lt;h5 id="Explain"&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-explain.html" rel="nofollow" target="_blank" title=""&gt;Explain&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;看上去 TF/IDF 的算法已经一脸懵逼吓跑人了，不过其实，用 Explain 跑一跑也没啥，虽然各种开方，自然对数的，Google 一个科学计算器就是了。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;举个例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*先删掉索引, 如果有的话*/&lt;/span&gt;
&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;XDELETE&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:9200/blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;XPUT&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:9200/blog/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;
{
  "mappings": {
     "post": {
        "properties": {
           "title": {
              "type": "string",
              "analyzer": "standard",
              "term_vector": "yes"
           }
        }
     }
  }
}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存入一些文档 (Water 随手加进去测试的.)&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;XPOST&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9200&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;_bulk&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;
{ "create": { "_index": "blog", "_type": "post", "_id": "1" }}
{ "title": "What is the best water temperature, Mr Water" }
{ "create": { "_index": "blog", "_type": "post", "_id": "2" }}
{ "title": "Water no symptoms" }
{ "create": { "_index": "blog", "_type": "post", "_id": "3" }}
{ "title": "Did Vitamin B6 alone work for you? Water?" }
{ "create": { "_index": "blog", "_type": "post", "_id": "4" }}
{ "title": "The ball drifted on the water." }
{ "create": { "_index": "blog", "_type": "post", "_id": "5" }}
{ "title": "No water no food no air" }
&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bulk insert 以后先用 Kopf 插件输出看一下，5 个文档并不是平均分配在 5 个分片的，其中，编号为 2 的这个分片里边有两个文档，其中编号为 0 的那个分片是没有分配文档在里面的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ww3.sinaimg.cn/large/62fdd4d5gw1f9fb44fanfj227w17un6g.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;接下来，搜索的同时 &lt;strong&gt;explain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;原本输出的 json 即使加了 pretty 也很难看，换成 yaml 会好不少&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-XGET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:9200/blog/post/_search?explain&amp;amp;format=yaml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"water"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出如图 (json)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ww3.sinaimg.cn/large/62fdd4d5gw1f9fb43tttmj21hu1dyah4.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可以看到五个文档都命中了这个查询，注意看每个文档的 &lt;strong&gt;_shard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;整个输出 yml 太长了，丢到最后面，只截取了其中一部分，如图，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ww4.sinaimg.cn/large/62fdd4d5gw1f9fb44r1ukj217018gtft.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;返回排名第一的分数是 &lt;strong&gt;_score: 0.2972674&lt;/strong&gt;, _shard(2),&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"weight(title:water in 0) [PerFieldSimilarity], result of:"&lt;/strong&gt; 这里的 0 不是 _id, 只是 Lucene 的一个内部文档 ID, 可以忽略。&lt;/p&gt;

&lt;p&gt;排名第一和第二的两个文档刚好是在同一个分片的，所以跟另外三个的返回结果有些许不一样，主要就是多了一个 &lt;strong&gt;queryWeight&lt;/strong&gt;, 里面的 &lt;strong&gt;queryNorm&lt;/strong&gt; 只要在同一分片下，都是一样的，总而言之，这个可以忽略 (至少目前这个例子可以忽略)&lt;/p&gt;

&lt;p&gt;只关注 &lt;strong&gt;fieldWeight&lt;/strong&gt;, 排名第一和第二的的 tf 都是 1,&lt;/p&gt;

&lt;p&gt;在 &lt;strong&gt;idf(docFreq=2, maxDocs=2)&lt;/strong&gt; 中，docFreq 和 maxDocs 都是针对单个分片而言，2 号分片一共有 2 个文档 (maxDocs), 然后命中的文档也是两个 (docFreq).&lt;/p&gt;

&lt;p&gt;所以 idf 的得分，根据公式，&lt;strong&gt;1 + ln(maxDocs/(docFreq + 1))&lt;/strong&gt; 是 0.59453489189&lt;/p&gt;

&lt;p&gt;最后 &lt;strong&gt;fieldNorm&lt;/strong&gt;, 这个 field 有三个词，所以是 1/sqrt(3), 但是按官方给的这个公式怎么算都不对，不管哪个文档。后来查了一下，说是 Lucene 存这个 lengthNorm 数据时候都是用的 1 byte 来存，所以不管怎么着都会丢掉一些精度。呵呵哒了 = . =&lt;/p&gt;

&lt;p&gt;最后的最后，总得分 = 1 * 0.5945349 * 0.5 = 0.2972674.&lt;/p&gt;

&lt;p&gt;同理其他的几个文档也可以算出这个得分，只是都要被 fieldNorm 的精度问题蛋疼一把。&lt;/p&gt;

&lt;p&gt;完整结果太长了，贴个 gist&lt;br&gt;
&lt;a href="https://gist.github.com/xguox/077e18afe24f52f6e2b45efb0b4e304f" rel="nofollow" target="_blank" title=""&gt;https://gist.github.com/xguox/077e18afe24f52f6e2b45efb0b4e304f&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="Elasticsearch 5 (Lucene 6) 的 BM25 算法"&gt;Elasticsearch 5 (Lucene 6) 的 BM25 算法&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Elasticsearch&lt;/strong&gt; &lt;a href="https://www.elastic.co/blog/elasticsearch-5-0-0-released" rel="nofollow" target="_blank" title=""&gt;前不久发布了 5.0 版本&lt;/a&gt;, 基于 Lucene 6, 默认使用了 &lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="nofollow" target="_blank" title=""&gt;BM25&lt;/a&gt; 评分算法。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BM25&lt;/strong&gt; 的 &lt;strong&gt;BM 是缩写自 Best Match&lt;/strong&gt;, 25 貌似是经过 25 次迭代调整之后得出的算法。它也是基于 &lt;strong&gt;TF/IDF&lt;/strong&gt; 进化来的。&lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="nofollow" target="_blank" title=""&gt;Wikipedia 那个公式&lt;/a&gt;看起来很吓唬人，尤其是那个求和符号，不过分解开来也是比较好理解的。&lt;/p&gt;

&lt;p&gt;总体而言，主要还是分三部分，&lt;strong&gt;TF - IDF - Document Length&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ww2.sinaimg.cn/large/62fdd4d5jw1fauy0sa5z2j20go023jrd.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;IDF 还是和&lt;a href="http://xguox.me/how-elasticsearch-scoring-document.html#inverse-document-frequency" rel="nofollow" target="_blank" title=""&gt;之前的一样&lt;/a&gt;. 公式 IDF(q) = 1 + ln(maxDocs/(docFreq + 1))&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;f(q, D) 是 tf(term frequency)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;|d| 是文档的长度，avgdl 是平均文档长度。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;先不看 IDF 和 Document Length 的部分，变成 &lt;strong&gt;tf * (k + 1) / (tf + k)&lt;/strong&gt;,&lt;/p&gt;

&lt;p&gt;相比传统的 TF/IDF (&lt;strong&gt;tf(q in d) = sqrt(termFreq)&lt;/strong&gt;) 而言，
 &lt;strong&gt;BM25 抑制了 tf 对整体评分的影响程度&lt;/strong&gt;, 虽然同样都是增函数，但是，BM25 中，tf 越大，带来的影响无限趋近于 (k + 1), 这里 k 值通常取 [1.2, 2], 而传统的 &lt;strong&gt;TF/IDF&lt;/strong&gt; 则会没有临界点的无限增长。&lt;/p&gt;

&lt;p&gt;而&lt;strong&gt;文档长度的影响&lt;/strong&gt;, 同样的，可以看到，命中搜索词的情况下，文档越短，相关性越高，具体影响程度又可以由公式中的 b 来调整，当设值为 0 的时候，就跟之前 '&lt;a href="http://xguox.me/how-elasticsearch-scoring-document.html#field-length-norm" rel="nofollow" target="_blank" title=""&gt;TF/IDF&lt;/a&gt;' 那篇提到的 &lt;code&gt;"norms": { "enabled": false }&lt;/code&gt; 一样，忽略文档长度的影响。&lt;/p&gt;

&lt;p&gt;综合起来，&lt;/p&gt;

&lt;p&gt;k = 1.2&lt;/p&gt;

&lt;p&gt;b = 0.75&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;idf * (tf * (k + 1)) / (tf + k * (1 - b + b * (|d|/avgdl)))&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最后再对所有的 terms 求和。就是 Elasticsearch 5 中一般查询的得分了。&lt;/p&gt;

&lt;hr&gt;
&lt;h5 id="Related:"&gt;Related:&lt;/h5&gt;
&lt;p&gt;&lt;a href="http://opensourceconnections.com/blog/2015/10/16/bm25-the-next-generation-of-lucene-relevation/" rel="nofollow" target="_blank" title=""&gt;http://opensourceconnections.com/blog/2015/10/16/bm25-the-next-generation-of-lucene-relevation/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/relevance-intro.html" rel="nofollow" target="_blank" title=""&gt;What Is Relevance?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://xguox.me/how-elasticsearch-scoring-document.html" rel="nofollow" target="_blank" title=""&gt;Elasticsearch 的分数 (_score) 是怎么计算得出&lt;/a&gt;&lt;br&gt;
&lt;a href="http://xguox.me/how-elasticsearch-5-scoring-with-bm25.html" rel="nofollow" target="_blank" title=""&gt;Elasticsearch 5.X(Lucene 6) 的 BM25 相关度算法&lt;/a&gt;&lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Mon, 19 Dec 2016 15:41:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/31934</link>
      <guid>https://ruby-china.org/topics/31934</guid>
    </item>
    <item>
      <title>使用 Ruby 处理大型 CSV 文件</title>
      <description>&lt;ul&gt;
&lt;li&gt;原文来自：&lt;a href="http://dalibornasevic.com/posts/68-processing-large-csv-files-with-ruby" rel="nofollow" target="_blank" title=""&gt;http://dalibornasevic.com/posts/68-processing-large-csv-files-with-ruby&lt;/a&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;译文链接：&lt;a href="http://xguox.me/processing-large-csv-with-ruby.html" rel="nofollow" target="_blank" title=""&gt;http://xguox.me/processing-large-csv-with-ruby.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;早些个月看到的一篇，翻译了以后好久其实也好像没啥机会正式用到，今天刚好要处理一个 100+ MB 的 CSV 文件，想起了这篇。跑的时候顺手打开监视器看看内存使用什么的，个别方法都跑到 6G + 内存了 Σ( ° △ °|||)︴&lt;/p&gt;

&lt;p&gt;处理大文件是一项非常耗内存的操作，有时候甚至会跑光服务器上的物理内存和虚拟内存。下面来看看使用 &lt;strong&gt;Ruby 来处理大型 CSV 文件&lt;/strong&gt;的几种方式，同时测试一下这几种方式的内存消耗以及性能。&lt;/p&gt;
&lt;h3 id="准备测试用的 CSV 数据文件."&gt;准备测试用的 CSV 数据文件。&lt;/h3&gt;
&lt;p&gt;在开始之前，先准备一个拥有一百万行的 CSV 文件 &lt;strong&gt;data.csv&lt;/strong&gt;(大约 75mb) 用于测试。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# generate_csv.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'city'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'street'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'country'&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="s2"&gt;"Pink Panther"&lt;/span&gt;
&lt;span class="n"&gt;email&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pink.panther@example.com"&lt;/span&gt;
&lt;span class="n"&gt;city&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Pink City"&lt;/span&gt;
&lt;span class="n"&gt;street&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Pink Road"&lt;/span&gt;
&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Pink Country"&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CSV&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="s1"&gt;'data.csv'&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="ss"&gt;write_headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;headers&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;csv&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="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;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;csv&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;i&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="内存及时间的消耗"&gt;内存及时间的消耗&lt;/h3&gt;
&lt;p&gt;上面这个脚本需要引用到 &lt;code&gt;helpers.rb&lt;/code&gt; 脚本，&lt;code&gt;helpers.rb&lt;/code&gt; 定义了两个 helper 方法来测量并打印出内存以及时间的消耗情况。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# helpers.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_memory_usage&lt;/span&gt;
  &lt;span class="n"&gt;memory_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`ps -o rss= -p &lt;/span&gt;&lt;span class="si"&gt;#{&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;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="n"&gt;memory_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`ps -o rss= -p &lt;/span&gt;&lt;span class="si"&gt;#{&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;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Memory: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;memory_after&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;memory_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;round&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="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB"&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;print_time_spent&lt;/span&gt;
  &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&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;realtime&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Time: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&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="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行并生成 CSV 文件：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;generate_csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.14&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.79&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不同的机器输出结果也不尽相同，不过，幸运的是，得益于 garbage collector (GC) 回收已使用过的内存，这个 Ruby 进程耗掉的内存 (4.79MB) 并不是很夸张。同时生成的数据文件如图为 74MB.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://ww2.sinaimg.cn/large/62fdd4d5jw1f2m3u6s1twj20ze038gm6.jpg" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="CSV.read"&gt;CSV.read&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse1.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CSV&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="s1"&gt;'data.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;csv&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;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Sum: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parse1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;

&lt;span class="no"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;499999500000&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.3&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1277.07&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;惊人的超过 1GB 内存消耗啊。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CSV.read&lt;/code&gt; 源码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="c1"&gt;# File csv.rb, line 1750&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;
  &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;to_a&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@use_headers&lt;/span&gt;
    &lt;span class="no"&gt;Table&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;rows&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;h3 id="CSV.parse"&gt;CSV.parse&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse2.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;content&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;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.csv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;csv&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;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Sum: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parse2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;499999500000&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.88&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1362.89&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，内存消耗的比刚刚第一个脚本还略多，差距大概就是刚好 CSV 文件大小。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CSV.parse&lt;/code&gt; 源码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# File csv.rb, line 1293&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;  &lt;span class="c1"&gt;# slurp contents, if no block is given&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;           &lt;span class="c1"&gt;# or pass each row to a provided block&lt;/span&gt;
    &lt;span class="n"&gt;csv&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="n"&gt;block&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;h3 id="CSV.new"&gt;CSV.new&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse3.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;content&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;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.csv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CSV&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Sum: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parse3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;499999500000&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;16.89&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76.72&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次结果可以看到，因为只是在内存中加载了整个文件内容，所以内存的消耗大概就是文件的大小 (74MB), 而处理时间也快了不少。当我们只是想逐行逐行的操作而不是读取要一次过读取一整个文件时候，这种方法非常奏效。&lt;/p&gt;
&lt;h3 id="通过 IO 对象一行一行解析"&gt;通过 IO 对象一行一行解析&lt;/h3&gt;
&lt;p&gt;虽然有了很大的进步，但是，使用 IO 文件对象还可以做得更好。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse4.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&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="s1"&gt;'data.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&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;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CSV&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;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
        &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Sum: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;执行结果：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parse4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;499999500000&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.78&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.64&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仅仅用了 2.64MB, 速度也稍稍又快了一些。&lt;/p&gt;
&lt;h3 id="CSV.foreach"&gt;CSV.foreach&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parse5.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'./helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="n"&gt;print_memory_usage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;print_time_spent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Sum: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;parse5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="no"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;499999500000&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.54&lt;/span&gt;
&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.62&lt;/span&gt; &lt;span class="no"&gt;MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CSV.foreach&lt;/code&gt; 源码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# File csv.rb, line 1118&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&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="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;return&lt;/span&gt; &lt;span class="n"&gt;to_enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__method__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
  &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&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;csv&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;csv&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="n"&gt;block&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;</description>
      <author>xguox</author>
      <pubDate>Wed, 26 Oct 2016 22:24:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/31444</link>
      <guid>https://ruby-china.org/topics/31444</guid>
    </item>
    <item>
      <title>升级 Ubuntu 12.04 后的杯具</title>
      <description>&lt;p&gt;昨晚把 ubuntu 升级到了 12.04，结果全部项目都杯具了，运行时候  passenger 都是报错如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Error&lt;/span&gt; &lt;span class="ss"&gt;message:
&lt;/span&gt;&lt;span class="n"&gt;libmysqlclient_r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;so&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="n"&gt;cannot&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="ss"&gt;file: &lt;/span&gt;&lt;span class="no"&gt;No&lt;/span&gt; &lt;span class="n"&gt;such&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sr"&gt;/home/x&lt;/span&gt;&lt;span class="n"&gt;guox&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="vi"&gt;@rails&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;so&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;百度谷歌也没找着办法，也试着自己 down 了 libmysqlclient.so.16 这个文件，依旧无果，有大神帮托下嚜？thx~  &lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Sat, 28 Apr 2012 19:48:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/2969</link>
      <guid>https://ruby-china.org/topics/2969</guid>
    </item>
    <item>
      <title>搭建 ruby-china，半成功吧.</title>
      <description>&lt;p&gt;全程跟 github 下面的那个帮助文档差不多，也看了下&lt;a href="/bindiry" class="user-mention" title="@bindiry"&gt;&lt;i&gt;@&lt;/i&gt;bindiry&lt;/a&gt; 之前的一个帖。同样是&lt;/p&gt;
&lt;h2 id="sudo rake assets:prec﻿om﻿pile"&gt;sudo rake assets:prec﻿om﻿pile&lt;/h2&gt;
&lt;p&gt;的那个错误，不过我试过重装 MongoDB 就通过了。重装是直接用 ubuntu 的安装包安装的
And then&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;执行 thin start -O -C config/thin.yml这步报错：
/home/xguox/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/psych.rb:229:in `initialize': No such file or directory - config/thin.yml (Errno::ENOENT)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;缺少那个 thin.yml？没明白，不过直接跳过接后面的步骤都 OK，然后用 rails s 也能打开，不过端口是在 0.0.0.0:3000，localhost：3000 被我的另外一个 nginx 部署的项目用了。&lt;/p&gt;

&lt;p&gt;另外，每次执行命令貌似都会冒出：
DEPRECATION WARNING: ActiveSupport::Memoizable is deprecated and will be removed in future releases,simply use Ruby memoization pattern instead. 
貌似不影响吧。  &lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Sat, 24 Mar 2012 01:18:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/2100</link>
      <guid>https://ruby-china.org/topics/2100</guid>
    </item>
    <item>
      <title>routes 莫名的出错</title>
      <description>&lt;p&gt;&lt;strong&gt;localhost:3000/login&lt;/strong&gt; 显示下面的错误提示。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Routing&lt;/span&gt; &lt;span class="no"&gt;Error&lt;/span&gt;
&lt;span class="no"&gt;No&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"destroy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"sessions"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="no"&gt;Try&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;rake&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请问这是什么情况？SessionsController 有定义 destroy 这个 action 呀
PS:这是我跟着 Ruby on Rails Tutorial  写的一个小项目。StackOverflow 上貌似也有人提问，不过回答好像没看明白。貌似没正解&lt;/p&gt;

&lt;p&gt;rake routes 如下：   &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;   &lt;span class="n"&gt;users&lt;/span&gt;         &lt;span class="no"&gt;GET&lt;/span&gt;            &lt;span class="sr"&gt;/users(.:format)                users#index
                   POST              /use&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#create&lt;/span&gt;
   &lt;span class="n"&gt;new_user&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt;    &lt;span class="sr"&gt;/users/ne&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#new&lt;/span&gt;
  &lt;span class="n"&gt;edit_user&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt;    &lt;span class="sr"&gt;/users/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#edit&lt;/span&gt;
       &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt;    &lt;span class="sr"&gt;/users/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#show&lt;/span&gt;
            &lt;span class="no"&gt;PUT&lt;/span&gt;    &lt;span class="sr"&gt;/users/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#update&lt;/span&gt;
            &lt;span class="no"&gt;DELETE&lt;/span&gt; &lt;span class="sr"&gt;/users/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#destroy&lt;/span&gt;
   &lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="no"&gt;POST&lt;/span&gt;   &lt;span class="sr"&gt;/sessions(.:format)       sessions#create
new_session GET    /sessions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="c1"&gt;#new&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="no"&gt;DELETE&lt;/span&gt; &lt;span class="sr"&gt;/sessions/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="c1"&gt;#destroy&lt;/span&gt;
     &lt;span class="n"&gt;signup&lt;/span&gt;        &lt;span class="sr"&gt;/signup(.:format)         users#new
      login        /&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="c1"&gt;#new&lt;/span&gt;
     &lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="no"&gt;DELETE&lt;/span&gt; &lt;span class="sr"&gt;/logout(.:format)         sessions#destroy
       help        /&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="c1"&gt;#help&lt;/span&gt;
      &lt;span class="n"&gt;about&lt;/span&gt;        &lt;span class="sr"&gt;/about(.:format)          pages#about
    contact        /&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="c1"&gt;#contact&lt;/span&gt;
       &lt;span class="n"&gt;home&lt;/span&gt;        &lt;span class="sr"&gt;/home(.:format)           pages#home
     comics        /&lt;/span&gt;&lt;span class="n"&gt;comics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="c1"&gt;#comics&lt;/span&gt;
  &lt;span class="n"&gt;animation&lt;/span&gt;        &lt;span class="sr"&gt;/animation(.:format)      pages#animation
       root        /&lt;/span&gt;                         &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="c1"&gt;#home&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;routes.rb  如下&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s1"&gt;'/signup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'users#new'&lt;/span&gt;
&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s1"&gt;'/login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'sessions#new'&lt;/span&gt;
&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s1"&gt;'/logout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'sessions#destroy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;via: :delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>xguox</author>
      <pubDate>Tue, 06 Mar 2012 14:01:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/1645</link>
      <guid>https://ruby-china.org/topics/1645</guid>
    </item>
    <item>
      <title>Rspec 各种无法运行，求帮托</title>
      <description>&lt;p&gt;_&lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Fri, 02 Mar 2012 02:46:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/1546</link>
      <guid>https://ruby-china.org/topics/1546</guid>
    </item>
    <item>
      <title>如何卸载 Nginx</title>
      <description>&lt;p&gt;Ubuntu 下用
sudo passenger-install-nginx-module
安装的 Nginx 如何卸载掉，因为同时装了 Nginx 和 Apache 会老占用端口，要自己 CUT 掉进程。
PS:为嘛貌似更多人推 Nginx 而不是 Apache？个人感觉 Apache 更友好些。  &lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Thu, 01 Mar 2012 16:28:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/1533</link>
      <guid>https://ruby-china.org/topics/1533</guid>
    </item>
    <item>
      <title>很诡异的博客</title>
      <description>&lt;p&gt;&lt;a href="http://blog.xdite.net" rel="nofollow" target="_blank"&gt;http://blog.xdite.net&lt;/a&gt;
&lt;del&gt;deleted&lt;/del&gt;&lt;/p&gt;</description>
      <author>xguox</author>
      <pubDate>Fri, 09 Dec 2011 12:33:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/423</link>
      <guid>https://ruby-china.org/topics/423</guid>
    </item>
  </channel>
</rss>
