<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dengqinghua (dengqinghua)</title>
    <link>https://ruby-china.org/dengqinghua</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>Ruby 字符串解析引擎 Racc 和 Rex 分析</title>
      <description>&lt;h2 id="Racc"&gt;Racc&lt;/h2&gt;
&lt;p&gt;原文地址：&lt;a href="http://blog.dengqinghua.net/racc.html" rel="nofollow" target="_blank"&gt;http://blog.dengqinghua.net/racc.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/dengqinghua/roses/blob/master/source/racc.md" rel="nofollow" target="_blank"&gt;https://github.com/dengqinghua/roses/blob/master/source/racc.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个是 Racc 的使用示例介绍。&lt;/p&gt;

&lt;p&gt;阅读完该文档之后，您将了解到：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rex 和 Racc 的关系&lt;/li&gt;
&lt;li&gt;如何使用 Racc 进行自定义&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="TL;DR"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;我们在做数据中心的时候，&lt;a href="http://blog.dengqinghua.net/badge_system.html" rel="nofollow" target="_blank" title=""&gt;计算引擎&lt;/a&gt;都是基于 SQL 的。&lt;/p&gt;

&lt;p&gt;在 Java 生态中，我们有很多解析 SQL 的工具，如 &lt;a href="https://github.com/alibaba/druid" rel="nofollow" target="_blank" title=""&gt;Druid&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ruby 中我们使用 &lt;a href="https://github.com/cryodex/sql-parser" rel="nofollow" target="_blank" title=""&gt;sql-parser&lt;/a&gt;&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;'sql-parser'&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SQLParser&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="c1"&gt;# Build the AST from a SQL statement&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT * FROM users WHERE id = 1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Find which columns where selected in the FROM clause&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "*"&lt;/span&gt;

&lt;span class="c1"&gt;# Output the table expression as SQL&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "FROM users WHERE id = 1"&lt;/span&gt;

&lt;span class="c1"&gt;# Drill down into the WHERE clause, to examine every piece&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where_clause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "WHERE id = 1"&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where_clause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_condition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "id = 1"&lt;/span&gt;
&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where_clause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_condition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是发现该功能的 SQL 解析能力有限，我们使用了很多&lt;code&gt;SQL函数&lt;/code&gt;, &lt;strong&gt;该插件并不支持复杂的 SQL 函数&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;我们希望可以对此插件进行扩展。阅读源码后发现，源码核心是由两个文件&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cryodex/sql-parser/blob/master/lib/sql-parser/parser.racc" rel="nofollow" target="_blank" title=""&gt;parser.racc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cryodex/sql-parser/blob/master/lib/sql-parser/parser.rex" rel="nofollow" target="_blank" title=""&gt;parser.rex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;阅读了一下相关资料，得知 &lt;code&gt;Yacc&lt;/code&gt; 和 &lt;code&gt;Rexical&lt;/code&gt; 的概念&lt;/p&gt;

&lt;p&gt;也就是说 SQL 解析分为两部分&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;patterns, 用 &lt;code&gt;parser.rex&lt;/code&gt; 来配置，如 SELECT, ORDER 等词汇&lt;/li&gt;
&lt;li&gt;grammar,  用 &lt;code&gt;parser.racc&lt;/code&gt; 来配置，定义了语法，即 SELECT, ORDER 的组合方式&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;故遇到一条 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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&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;的时候，会分析出 patterns&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再根据 &lt;code&gt;语法&lt;/code&gt; 进行正则匹配即可。&lt;/p&gt;

&lt;p&gt;下面的文档是介绍了 Rex 和 Racc 的作用 和 使用方式。&lt;/p&gt;

&lt;p&gt;示例代码可以在 &lt;a href="https://github.com/dengqinghua/my_examples/tree/master/ruby/racc" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt; 查看&lt;/p&gt;
&lt;h2 id="Rex 和 Racc 的来源"&gt;Rex 和 Racc 的来源&lt;/h2&gt;&lt;h3 id="Yacc和Lexical"&gt;Yacc 和 Lexical&lt;/h3&gt;
&lt;p&gt;Racc 和 Rex 分别来源于单词 &lt;a href="https://en.wikipedia.org/wiki/Yacc" rel="nofollow" target="_blank" title=""&gt;Yacc&lt;/a&gt; 和 &lt;code&gt;Lexical Analyser&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;在我们定义一个语言的时候，需要解决两个问题&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;有哪些词汇 (patterns)?&lt;/li&gt;
&lt;li&gt;有哪些语法 (grammar)?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中 Lexical 就是&lt;code&gt;词汇&lt;/code&gt;的抽象，Yacc 就是&lt;code&gt;语法&lt;/code&gt;的抽象&lt;/p&gt;

&lt;p&gt;下面是一个语法解析的过程示例：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/dengqinghua/roses/master/assets/images/racc_example.png" title="" alt="racc_example"&gt;&lt;/p&gt;

&lt;p&gt;图片和对应的文档来源于&lt;a href="http://epaperpress.com/lexandyacc/intro.html" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;对应 Racc 和 Rex, 也是做相同的事情，仅仅是解析和定义的语言为 Ruby, 所以用 R 开头&lt;/p&gt;
&lt;h3 id="Git地址"&gt;Git 地址&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tenderlove/racc" rel="nofollow" target="_blank" title=""&gt;Racc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tenderlove/rexical" rel="nofollow" target="_blank" title=""&gt;Rexical&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Rex"&gt;Rex&lt;/h2&gt;&lt;h3 id="示例"&gt;示例&lt;/h3&gt;
&lt;p&gt;在示例代码 &lt;a href="https://github.com/dengqinghua/my_examples/blob/master/ruby/racc/calculator.rex" rel="nofollow" target="_blank" title=""&gt;calculator.rex&lt;/a&gt; 定义了词汇&lt;/p&gt;

&lt;p&gt;如规则&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;macro&lt;/span&gt;
  &lt;span class="no"&gt;DIGIT_MACRO&lt;/span&gt;     &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;

&lt;span class="n"&gt;rule&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;DIGIT_MACRO&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="ss"&gt;:DIGIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示的是 遇到了数字，那么解析成 [:DIGIT, 数字] 的形式，即&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;输入 1024, 解析为词汇 [:DIGIT, 1024]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么要有一个 &lt;code&gt;:DIGIT&lt;/code&gt; 的标识呢？是因为我们需要将相同属性的东西进行标记，下一步进行语法申明的时候可以用到&lt;/p&gt;

&lt;p&gt;NOTE: 参考：&lt;a href="http://testerstories.com/2012/06/a-tester-learns-rex-and-racc-part-1/" rel="nofollow" target="_blank" title=""&gt;a-tester-learns-rex-and-racc-part-1&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Racc"&gt;Racc&lt;/h2&gt;&lt;h3 id="示例"&gt;示例&lt;/h3&gt;
&lt;p&gt;有了词汇之后，我们就可以定义语法了。&lt;/p&gt;

&lt;p&gt;在示例代码 &lt;a href="https://github.com/dengqinghua/my_examples/blob/master/ruby/racc/calculator.y" rel="nofollow" target="_blank" title=""&gt;calculator.y&lt;/a&gt; 定义了语法&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rule&lt;/span&gt;
  &lt;span class="n"&gt;expression&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="no"&gt;DIGIT&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;DIGIT&lt;/span&gt; &lt;span class="no"&gt;ADD&lt;/span&gt; &lt;span class="no"&gt;DIGIT&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;val&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;val&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="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;DIGIT&lt;/span&gt; &lt;span class="no"&gt;SUBSTRACT&lt;/span&gt; &lt;span class="no"&gt;DIGIT&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;val&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;val&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="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;DIGIT&lt;/span&gt; &lt;span class="no"&gt;MULTIPLY&lt;/span&gt; &lt;span class="no"&gt;DIGIT&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;val&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;val&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="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;DIGIT&lt;/span&gt; &lt;span class="no"&gt;DIVIDE&lt;/span&gt; &lt;span class="no"&gt;DIGIT&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;val&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;val&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="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;ol&gt;
&lt;li&gt;expression 仅仅是一个名词，可以起名为 abc, bcd 都行，他可以被复用，即规则可以嵌套使用。 ":"后面是代表各种不同的情况，| 代表 或&lt;/li&gt;
&lt;li&gt;DIGIT ADD SUBSTRACT 等，均为 calculator.rex 中申明的词汇 (patterns)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;val 是指匹配成功之后，对应的值&lt;/p&gt;

&lt;p&gt;如字符串 &lt;code&gt;2 + 2&lt;/code&gt;, 匹配到了 &lt;code&gt;DIGIT ADD DIGIT&lt;/code&gt; 这部分，则&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;val&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;2&lt;/span&gt;
&lt;span class="n"&gt;val&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="s2"&gt;"+"&lt;/span&gt;
&lt;span class="n"&gt;val&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="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;{} 表示的是 ruby 代码，即如何处理该语法。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="编译"&gt;编译&lt;/h2&gt;
&lt;p&gt;编译的过程是生成 ruby 代码的过程，包括 rex 和 racc 两部分&lt;/p&gt;

&lt;p&gt;执行&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rex calculator.rex &lt;span class="nt"&gt;-o&lt;/span&gt; calculator.rex.rb
racc calculator.y &lt;span class="nt"&gt;-o&lt;/span&gt; calculator.racc.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NOTE: 在这之前需要安装 racc 这个 gem. 可以通过 &lt;code&gt;gem install racc&lt;/code&gt; 进行安装&lt;/p&gt;

&lt;p&gt;此时有一个 &lt;code&gt;calculator.racc.rb&lt;/code&gt; 文件。我们可以起一个 pry 进行测试&lt;/p&gt;

&lt;p&gt;NOTE: 希望之后可以添加 Rspec 测试，这样更加直观和规范&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pry &lt;span class="nt"&gt;-r&lt;/span&gt; ./calculator.racc.rb
&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="no"&gt;Calculator&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2 + 2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 输出 4&lt;/span&gt;
&lt;span class="no"&gt;Calculator&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2 - 2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 输出 0&lt;/span&gt;
&lt;span class="no"&gt;Calculator&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2 * 3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 输出 6&lt;/span&gt;
&lt;span class="no"&gt;Calculator&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2 / 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 输出 2&lt;/span&gt;
&lt;span class="no"&gt;Calculator&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2 | 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 抛出异常, 因为 | 无法解析: parse error on value 2 (DIGIT)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到对应的结果。&lt;/p&gt;
&lt;h2 id="References"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://epaperpress.com/lexandyacc/intro.html" rel="nofollow" target="_blank" title=""&gt;Lex and Yacc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://testerstories.com/category/language-building/rex-and-racc/" rel="nofollow" target="_blank" title=""&gt;Rex-and-Racc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>dengqinghua</author>
      <pubDate>Tue, 29 May 2018 10:53:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/36854</link>
      <guid>https://ruby-china.org/topics/36854</guid>
    </item>
    <item>
      <title>基于 Sidekiq 的异步任务管理引擎设计</title>
      <description>&lt;h2 id="基于Sidekiq的异步任务管理引擎"&gt;基于 Sidekiq 的异步任务管理引擎&lt;/h2&gt;
&lt;p&gt;原文地址：&lt;a href="http://blog.dengqinghua.net/sidekiq_task_event.html" rel="nofollow" target="_blank"&gt;http://blog.dengqinghua.net/sidekiq_task_event.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/dengqinghua/roses/blob/master/source/sidekiq_task_event.md" rel="nofollow" target="_blank"&gt;https://github.com/dengqinghua/roses/blob/master/source/sidekiq_task_event.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们在项目中大量使用到了&lt;a href="https://github.com/mperham/sidekiq" rel="nofollow" target="_blank" title=""&gt;Sidekiq&lt;/a&gt;
作为队列任务处理，但是 Sidekiq 无法获取到每一个任务的处理情况。&lt;/p&gt;

&lt;p&gt;在系统中有一类问题的抽象为：&lt;/p&gt;

&lt;p&gt;批量处理 n 个任务，每个任务都比较耗时，希望可以快速地处理，并且能知道每一个任务的执行结果情况。&lt;/p&gt;

&lt;p&gt;基于这类问题，我们研发了基于 Sidekiq 的异步任务管理引擎。&lt;/p&gt;

&lt;p&gt;阅读完该文档之后，您将了解到：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sidekiq 基本框架源码分析。&lt;/li&gt;
&lt;li&gt;Sidekiq Middleware.&lt;/li&gt;
&lt;li&gt;异步任务管理引擎设计。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="Sidekiq基本框架"&gt;Sidekiq 基本框架&lt;/h2&gt;
&lt;p&gt;Sidekiq 基于 Redis 作为存储，一个例子如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/dengqinghua/roses/master/assets/images/sidekiq_exmaple.png" title="" alt="sidekiq_exmaple"&gt;&lt;/p&gt;
&lt;h2 id="Sidekiq Client"&gt;Sidekiq Client&lt;/h2&gt;
&lt;p&gt;Sidekiq Client 部分为队列数据的生产者，在 Sidekiq 源码中可以看到&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sidekiq&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;push&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="n"&gt;normed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize_item&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process_single&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="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;normed&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;payload&lt;/span&gt;
        &lt;span class="n"&gt;raw_push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jid'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&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;def&lt;/span&gt; &lt;span class="nf"&gt;atomic_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payloads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payloads&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="s1"&gt;'queue'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
      &lt;span class="n"&gt;to_push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payloads&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;entry&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="s1"&gt;'enqueued_at'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
        &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump_json&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="k"&gt;end&lt;/span&gt;
      &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'queues'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lpush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"queue:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;q&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="n"&gt;to_push&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;最终会在 Redis 中存储下面这些信息&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry 重试次数&lt;/li&gt;
&lt;li&gt;queue 队列名称&lt;/li&gt;
&lt;li&gt;backtrace 错误栈&lt;/li&gt;
&lt;li&gt;class 处理类名称&lt;/li&gt;
&lt;li&gt;args  参数&lt;/li&gt;
&lt;li&gt;jid   job_id&lt;/li&gt;
&lt;li&gt;enqueued_at 进入队列的时间&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;并将这些信息通过&lt;a href="https://redis.io/commands/lpush" rel="nofollow" target="_blank" title=""&gt;lpush&lt;/a&gt;存储在 Redis 的队列中。&lt;/p&gt;
&lt;h2 id="Sidekiq Server"&gt;Sidekiq Server&lt;/h2&gt;&lt;h3 id="Before 4.0"&gt;Before 4.0&lt;/h3&gt;
&lt;p&gt;Sidekiq4.0 之前，使用的是&lt;a href="https://github.com/celluloid/celluloid" rel="nofollow" target="_blank" title=""&gt;Celluloid&lt;/a&gt;作为多线程的抽象层&lt;/p&gt;

&lt;p&gt;模型如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/dengqinghua/roses/master/assets/images/sidekiq_actor_architecture.png" title="" alt="sidekiq_actor_architecture"&gt;&lt;/p&gt;

&lt;p&gt;源码分析请参考 &lt;a href="https://www.jstorimer.com/products/working-with-ruby-threads" rel="nofollow" target="_blank" title=""&gt;Working With Ruby Threads-Chapter 15&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="After 4.0"&gt;After 4.0&lt;/h3&gt;
&lt;p&gt;在 4.0 版本之后，Sidekiq 出于性能考虑，使用原生的&lt;code&gt;Thread&lt;/code&gt;实现了一个简易的 Actor 版本模型。相关文章请见&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.mikeperham.com/2015/10/14/should-you-use-celluloid/" rel="nofollow" target="_blank" title=""&gt;Should you use Celluloid?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mperham/sidekiq/issues/2583" rel="nofollow" target="_blank" title=""&gt;Could Sidekiq Use concurrent-ruby?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;模型如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/dengqinghua/roses/master/assets/images/sidekiq_new_framework.png" title="" alt="sidekiq_new_framework"&gt;&lt;/p&gt;

&lt;p&gt;核心的组件包括&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Manager&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Manager&lt;/code&gt; 根据用户设置的并发数，生成处理队列任务的 &lt;code&gt;Processor&lt;/code&gt;, 并对 idle 或者 dead 的 &lt;code&gt;Processsor&lt;/code&gt; 进行管理，包括：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Spin&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="no"&gt;Processors&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="nf"&gt;processor_died&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Handle&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;away&lt;/span&gt; &lt;span class="no"&gt;Processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;one&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;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shutdown&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt; &lt;span class="no"&gt;Processors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="mi"&gt;4&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="n"&gt;hard&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="no"&gt;Processors&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化 Manager&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;Manager&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;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&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="nf"&gt;inspect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="vi"&gt;@count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:concurrency&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Concurrency of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not supported"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# @done代表是否结束处理任务&lt;/span&gt;
    &lt;span class="vi"&gt;@done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="vi"&gt;@workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

    &lt;span class="c1"&gt;# 生成多个Processor, 每一个Processor对象在被调用start方法的时候, 会生成了一个线程&lt;/span&gt;
    &lt;span class="vi"&gt;@count.times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@workers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Processor&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="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# 添加一个锁, 用于修改 @workers 的数据, 管理Processor对象&lt;/span&gt;
    &lt;span class="vi"&gt;@plock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mutex&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 Manager, 即调用&lt;code&gt;Processor#start&lt;/code&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;Manager&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="vi"&gt;@workers.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;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;start&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;/li&gt;
&lt;li&gt;
&lt;p&gt;Processor&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Processor&lt;/code&gt; 是处理任务的类，包括下面的功能&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;fetches&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;brpop&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;executes&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;job&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;instantiate&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="no"&gt;Worker&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;#perform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Processor#start&lt;/code&gt;, 启动 Processor, 创建一个线程&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;Processor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="c1"&gt;# 生成一个线程, 并调用run方法&lt;/span&gt;
    &lt;span class="vi"&gt;@thread&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;safe_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"processor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:run&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;code&gt;Processor#run&lt;/code&gt;, 处理任务，去 Redis 获取队列数据&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# @mgr 即为他对应的 Manager 对象&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Processor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@done&lt;/span&gt;
        &lt;span class="c1"&gt;# 调用 perform 方法进行处理&lt;/span&gt;
        &lt;span class="n"&gt;process_one&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 一旦结束了, 则将 Processor对象中的manager对应的worker去掉, 即是改变了上述 Manager的 @workers 数组&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_stopped&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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Shutdown&lt;/span&gt;
      &lt;span class="c1"&gt;# 在接收到TERM SIGNAL之后, 等待超时的时候sidekiq会抛出异常 Sidekiq::Shutdown, 见下文分析&lt;/span&gt;
      &lt;span class="c1"&gt;# 线程被关闭.&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_stopped&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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
      &lt;span class="c1"&gt;# 程序报错了, Manager#processor_died 会重新生成一个新的Processor线程&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_died&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="n"&gt;ex&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;/li&gt;
&lt;/ol&gt;
&lt;h3 id="队列重启时job的处理"&gt;队列重启时 job 的处理&lt;/h3&gt;
&lt;p&gt;当我们更新代码后，需要重启&lt;code&gt;Sidekiq&lt;/code&gt;的进程。一般来说，我们会发送一个 &lt;code&gt;TERM SIGNAL&lt;/code&gt; 指令给 Sidekiq 进程，它的执行步骤如下&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;停止 Fetch jobs.&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;Manager&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quiet&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@done&lt;/span&gt;
    &lt;span class="c1"&gt;# 将 @done 设置为 true&lt;/span&gt;
    &lt;span class="vi"&gt;@done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Terminating quiet workers"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="vi"&gt;@workers.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;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;terminate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# 这里的每一个 x 都是一个Processor对象&lt;/span&gt;
    &lt;span class="n"&gt;fire_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:quiet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reverse: &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;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;Processsor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@done&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;# 将每一个Processor 的 @done 设置为 true, 下面的run方法则不再fetch新的job&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@thread&lt;/span&gt;
    &lt;span class="vi"&gt;@thread.value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wait&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;run&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@done&lt;/span&gt;
        &lt;span class="n"&gt;process_one&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="c1"&gt;# 一旦结束了, 则将 Processor对象中的manager对应的worker去掉, 即是改变了上述 Manager的 @workers 数组&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_stopped&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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Shutdown&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_stopped&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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
      &lt;span class="vi"&gt;@mgr.processor_died&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="n"&gt;ex&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;/li&gt;
&lt;li&gt;
&lt;p&gt;等待&lt;code&gt;Sidekiq.options[:timeout]&lt;/code&gt;秒 (默认为 8 秒) 的时间，使得 Processor 去处理完当前未完成的 jobs&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;Manager&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;quiet&lt;/span&gt;
    &lt;span class="n"&gt;fire_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:shutdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reverse: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# some of the shutdown events can be async,&lt;/span&gt;
    &lt;span class="c1"&gt;# we don't have any way to know when they're done but&lt;/span&gt;
    &lt;span class="c1"&gt;# give them a little time to take effect&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="no"&gt;PAUSE_TIME&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@workers.empty&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Pausing to allow workers to finish..."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

    &lt;span class="c1"&gt;# 等待默认的8s后, 如果 @workers 为空, 则代表在规定时间内任务都处理完, 退出&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;PAUSE_TIME&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@workers.empty&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="no"&gt;PAUSE_TIME&lt;/span&gt;
      &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@workers.empty&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="c1"&gt;# 等待默认的8s后, 如果 @workers 不为空, 则进行强制shutdown&lt;/span&gt;
    &lt;span class="n"&gt;hard_shutdown&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;/li&gt;
&lt;li&gt;
&lt;p&gt;如果在等待时间之后，仍存在正在处理的 job, 则将 job 通过 rpush 命令推入 Redis, 强制使 processor 退出&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;Manager&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hard_shutdown&lt;/span&gt;
    &lt;span class="c1"&gt;# We've reached the timeout and we still have busy workers.&lt;/span&gt;
    &lt;span class="c1"&gt;# They must die but their jobs shall live on.&lt;/span&gt;
    &lt;span class="n"&gt;cleanup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="vi"&gt;@plock.synchronize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;cleanup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@workers.dup&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&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="c1"&gt;# 获取没有处理完的job&lt;/span&gt;
      &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleanup&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="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;job&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;compact&lt;/span&gt;

      &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Terminating &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; busy worker threads"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Work still in progress &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&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="c1"&gt;# Re-enqueue unfinished jobs&lt;/span&gt;
      &lt;span class="c1"&gt;# NOTE: You may notice that we may push a job back to redis before&lt;/span&gt;
      &lt;span class="c1"&gt;# the worker thread is terminated. This is ok because Sidekiq's&lt;/span&gt;
      &lt;span class="c1"&gt;# contract says that jobs are run AT LEAST once. Process termination&lt;/span&gt;
      &lt;span class="c1"&gt;# is delayed until we're certain the jobs are back in Redis because&lt;/span&gt;
      &lt;span class="c1"&gt;# it is worse to lose a job than to run it twice.&lt;/span&gt;
      &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:fetch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BasicFetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# 将未处理完的jobs推入队列的头部&lt;/span&gt;
      &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bulk_requeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@options&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;# 强制kill掉线程&lt;/span&gt;
    &lt;span class="n"&gt;cleanup&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;processor&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&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;Processor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@done&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;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@thread&lt;/span&gt;
    &lt;span class="c1"&gt;# unlike the other actors, terminate does not wait&lt;/span&gt;
    &lt;span class="c1"&gt;# for the thread to finish because we don't know how&lt;/span&gt;
    &lt;span class="c1"&gt;# long the job will take to finish.  Instead we&lt;/span&gt;
    &lt;span class="c1"&gt;# provide a `kill` method to call after the shutdown&lt;/span&gt;
    &lt;span class="c1"&gt;# timeout passes.&lt;/span&gt;
    &lt;span class="vi"&gt;@thread.raise&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Shutdown&lt;/span&gt;
    &lt;span class="vi"&gt;@thread.value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wait&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;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NOTE: 注意在接收到&lt;code&gt;TERM SIGNAL&lt;/code&gt;一些 job 有可能被重复执行。Sidekiq 的 FAQ 中有说明：&lt;strong&gt;Remember that Sidekiq will run your jobs AT LEAST once&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;INFO: Sidekiq 还提供了 Scheduling Job 的功能，即到时执行任务，该部分使用了一个 SortedSet 的 redis 数据结构，排序的因子为任务的执行时间。在启动 Sidekiq 服务的时候，会启动了一个线程轮询所有执行时间小于等于当前时间的队列数据，将该部分的数据在 pop 至队列，再由 Processor 处理。&lt;/p&gt;
&lt;h2 id="Sidekiq Middleware"&gt;Sidekiq Middleware&lt;/h2&gt;
&lt;p&gt;Sidekiq 在 client-side 和 server-side 都支持 AOP 操作，该部分和&lt;a href="https://rack.github.io/" rel="nofollow" target="_blank" title=""&gt;Rack&lt;/a&gt;的原理一致。&lt;/p&gt;

&lt;p&gt;有了&lt;code&gt;server-side middleware&lt;/code&gt;的支持，我们可以&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;在sidekiq处理任务前后, 捕捉到任务的处理情况
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如 Sidekiq 提供了 &lt;code&gt;ActiveRecord&lt;/code&gt; 的 &lt;code&gt;server-side middleware&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sidekiq&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Middleware&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveRecord&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
          &lt;span class="c1"&gt;# With Rails 5+ we must use the Reloader **always**.&lt;/span&gt;
          &lt;span class="c1"&gt;# The reloader handles code loading and db connection management.&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rails&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="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Rails 5 no longer needs or uses the ActiveRecord middleware."&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;call&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;yield&lt;/span&gt;
        &lt;span class="k"&gt;ensure&lt;/span&gt;
          &lt;span class="o"&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="nf"&gt;clear_active_connections!&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于基于 Rails 的 Sidekiq 服务，Sidekiq 会确保在每次执行任务之后，都会清掉使用的连接，避免多线程占用过多的 Rails 数据库连接。&lt;/p&gt;
&lt;h2 id="AsyncTask"&gt;AsyncTask&lt;/h2&gt;&lt;h3 id="需求分析"&gt;需求分析&lt;/h3&gt;
&lt;p&gt;我们经常有一些这样的需求：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. 给卖家批量报名活动, 一次可以报名200个商品, 如果报名失败的记录, 需要有提示信息
2. 批量创建活动, 一次导入一个1万条商品的excel, 需要给这1万条数据创建
3. 批量导出50万大促信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最开始我们都是通过串行的方式进行处理，比如&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. 给卖家批量报名活动, 一次可以报名200个商品, 如果报名失败的记录, 需要有提示信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们提供一个商品的 HTTP 接口，然后由 JS 发 Ajax 请求进行调用，但是该方式有一些问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据容易丢失&lt;/li&gt;
&lt;li&gt;一些接口请求很慢，容易造成超时&lt;/li&gt;
&lt;li&gt;JS 交互复杂，大量的逻辑都放在了前端，出问题不好排查&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是对于数据量大的情况，串行调用变得非常慢，如&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2. 批量创建活动, 一次导入一个1万条商品的excel, 需要给这1万条数据创建
3. 批量导出50万大促信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们考虑使用 Sidekiq 进行处理，即每一个任务都放在 Redis 里面。调用 perform_async 方法，获取到任务的 job_id&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;job_id = ProductWorker.perform_async(params)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是新的问题出现了：我们无法获取到这个 job 的完成情况，如果逻辑上处理失败，也无法获取到对应的错误信息。&lt;/p&gt;

&lt;p&gt;NOTE: &lt;a href="https://sidekiq.org/products/pro.html" rel="nofollow" target="_blank" title=""&gt;Sidekiq-Pro&lt;/a&gt; 支持 batches 功能，但是它是收费的。&lt;/p&gt;

&lt;p&gt;我们最终决定利用 Sidekiq 的 Middleware 特性，研发出一套异步任务管理引擎，它支持&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;任务的聚合管理。一个 task 和多个 job 进行关联&lt;/li&gt;
&lt;li&gt;可以获得 job 的执行状态&lt;/li&gt;
&lt;li&gt;所有执行过程可视化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="AsyncTask"&gt;AsyncTask&lt;/h3&gt;
&lt;p&gt;任务处理引擎架构图&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/dengqinghua/roses/master/assets/images/async_task.png" title="" alt="async_task"&gt;&lt;/p&gt;

&lt;p&gt;它包含三部分&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建 Task, 生成 task_id, 将每一个任务都推入 Redis, 并获取到对应的 job_id&lt;/li&gt;
&lt;li&gt;生成 Event 记录，该 Event 和 job_id 一一对应，记录了整个 job 的生命周期&lt;/li&gt;
&lt;li&gt;利用 Server-Side Middleware, 记录 Event 的状态和相关信息&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NOTE: 步骤一的 job_id 由 Sidekiq 生成&lt;/p&gt;
&lt;h4 id="Task 和 Event 创建"&gt;Task 和 Event 创建&lt;/h4&gt;
&lt;p&gt;我们将 Task 和 Event 都创建了对应的数据库表，则&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;Task&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:events&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;Event&lt;/span&gt;
  &lt;span class="n"&gt;validates_uniqueness_of&lt;/span&gt; &lt;span class="ss"&gt;:job_id&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:task&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Task 的数据结构为&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;释义&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker_name&lt;/td&gt;
&lt;td&gt;worker 的名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;主键 id&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Event 的数据结构为&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;释义&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;job_id&lt;/td&gt;
&lt;td&gt;任务 id，全局唯一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;当前状态，包括&lt;code&gt;enqueue&lt;/code&gt;,&lt;code&gt;working&lt;/code&gt;, &lt;code&gt;finish&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;params&lt;/td&gt;
&lt;td&gt;任务执行的所有参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;added_messages&lt;/td&gt;
&lt;td&gt;增量的信息，记录整个任务的流程&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;NOTE: 注意到 status 包含了 falied 和 error 两个不同的状态。其中 failed 代表为 业务逻辑上的失败，如一个卖家因为资质不合格导致无法报名，为了获取该状态，处理时可直接抛出异常 (NormalException), 状态为 failed. 而 error 代表为系统错误，如程序 bug 或者接口超时等&lt;/p&gt;
&lt;h4 id="Server-Side Middleware"&gt;Server-Side Middleware&lt;/h4&gt;
&lt;p&gt;在这里我们配置了 &lt;code&gt;use_task_event&lt;/code&gt;, 如果需要使用该插件，需要在 worker 中配置 &lt;code&gt;use_task_event: true&lt;/code&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;AWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;use_task_event: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&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="n"&gt;handle_job&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server-Side Middleware 代码和注释如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;AsyncTask&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MiddlewareServer&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker&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="n"&gt;queue&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;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'use_task_event'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# 配置入口&lt;/span&gt;
        &lt;span class="k"&gt;begin&lt;/span&gt;
          &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jid'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:working&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"处理中"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;yield&lt;/span&gt;

          &lt;span class="c1"&gt;# 正常处理成功, 设置 status 为 finish&lt;/span&gt;
          &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"已经完成"&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;SystemExit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Interrupt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
          &lt;span class="c1"&gt;# 被中断, 设置 status 为 error&lt;/span&gt;
          &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"被中断"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="c1"&gt;# 如果之后会被重试, 则重新再设置为 :enqueue&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retry_status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Integer&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_status&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_status&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;retry_count&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="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:enqueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &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;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
        &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NormalException&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
          &lt;span class="c1"&gt;# 业务逻辑上的失败, 设置 status 为 failed, 错误信息放在 message 中&lt;/span&gt;
          &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"发生错误: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&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;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
          &lt;span class="c1"&gt;# 程序bug, 设置 status 为 error&lt;/span&gt;
          &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"发生致命错误: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&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="c1"&gt;# 如果之后会被重试, 则重新再设置为 :enqueue&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retry_status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Integer&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_status&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_status&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;retry_count&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="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:enqueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &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;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
        &lt;span class="k"&gt;end&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="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在项目启动时加载该 Middleware&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add AsyncTask::MiddlewareServer
  end
end
&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;1. 给卖家批量报名, 一次可以报名200个商品, 进行活动, 如果报名失败的记录, 需要有提示信息
2. 批量创建活动, 一次导入一个1万条商品的excel, 需要给这1万条数据创建
3. 批量导出50万大促信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于需求 1, 2, 都可以用相同的处理方式，流程如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;前端一次将所有的数据全部提到给后端。&lt;/li&gt;
&lt;li&gt;后端根据数据量拆分为 n 个 jobs, 并生成一个 task_id, 返回给前端。&lt;/li&gt;
&lt;li&gt;前端每隔一段时间，调用后端的接口来询问 task_id 对于的 job 的状态，如果出错，则一同返回错误信息。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于需求 3, 我们可以将 50 万信息分为不同的 worker 来处理，并用统一的 task_id 进行关联，也将大大提高导出的效率。&lt;/p&gt;</description>
      <author>dengqinghua</author>
      <pubDate>Mon, 28 May 2018 18:54:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/36850</link>
      <guid>https://ruby-china.org/topics/36850</guid>
    </item>
  </channel>
</rss>
