<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>memorycancel (memorycancel)</title>
    <link>https://ruby-china.org/memorycancel</link>
    <description>拿来吧你</description>
    <language>en-us</language>
    <item>
      <title>微调 Puma 部署服务器性能</title>
      <description>&lt;p&gt;此处讨论范围为 Rails 应用服务器，它是大多数网络应用程序中对性能敏感主要组件。
后台作业和 WebSockets 等其他组件也可以调整（数据库组件），但本文不涉及。&lt;/p&gt;

&lt;p&gt;有关如何配置应用程序更多信息，请参阅&lt;a href="https://guides.rubyonrails.org/configuring.html" rel="nofollow" target="_blank" title=""&gt;《配置指南》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;本指南假定运行是 MRI &lt;a href="https://en.wikipedia.org/wiki/Ruby_MRI" rel="nofollow" target="_blank" title=""&gt;Matz's Ruby Interpreter&lt;/a&gt; -（Ruby 典型实现，也称为 CRuby）。如果使用是 JRuby 或 TruffleRuby 等
其他 Ruby 实现，本指南大部分内容将不适用。如有需要，请查阅与 Ruby 实现相关资料。&lt;/p&gt;
&lt;h2 id="1 选择应用服务器"&gt;1 选择应用服务器&lt;/h2&gt;
&lt;p&gt;Puma 是 Rails 默认应用服务器，也是整个社区最常用服务器。它在大多数情况下都运行良好。
在某些情况下，可能希望更换为其他服务器。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;不同应用服务器使用特定并发方法。例如，Unicorn 使用进程，Puma 和 Passenger 是基于进程和线程混合并发，而 Falcon 则使用纤程。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全面讨论 Ruby 并发方法超出了本文范围，但本文将介绍进程和线程之间关键权衡。
如果想使用进程和线程以外方法，则需要使用不同应用程序服务器。本文将重点介绍如何调整 Puma。&lt;/p&gt;
&lt;h2 id="2 为什么要优化？"&gt;2 为什么要优化？&lt;/h2&gt;
&lt;p&gt;从本质上讲，调整 Ruby 网络服务器就是在内存使用率、吞吐量和延迟等多个属性之间做出权衡。
吞吐量衡量是服务器每秒能处理多少个请求，而延迟衡量是单个请求所需时间（也称为响应时间）。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;一些用户可能希望最大限度地提高吞吐量，以`降低托管成本`；另一些用户可能希望最大限度地减少延迟，以提供最佳用户体验；还有许多用户会在中间寻找折中方案。
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;重要是要明白，优化一种属性通常至少会损害另一种属性。
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.1 理解 Ruby 并发和并行"&gt;2.1 理解 Ruby 并发和并行&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;广义上，并发性是指通过在单个 CPU 内核上快速切换多个任务来管理看似同时发生多个任务能力，而并行性是指在多个 CPU 内核上同时执行多个任务，使它们真正并行运行；
从本质上讲，并发性是指管理多个任务，而并行性是指在多个处理器上同时执行这些任务。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://www.ruby-lang.org/en/" rel="nofollow" target="_blank" title=""&gt;CRuby&lt;/a&gt; 有一个&lt;a href="https://en.wikipedia.org/wiki/Global_interpreter_lock" rel="nofollow" target="_blank" title=""&gt;全局解释器锁&lt;/a&gt;，通常称为 GVL 或 GIL。GVL 可以防止多个线程在单个进程中同时运行 Ruby 
代码。多个线程可以等待网络数据、数据库操作或其他非 Ruby 工作（通常称为 I/O 操作），但一次只能有一个线程运行 Ruby 代码。&lt;/p&gt;

&lt;p&gt;这意味着基于线程并发性可以通过并发处理网络请求来提高吞吐量，但每当 I/O 
操作完成时，都可能会降低延迟。执行该操作线程可能需要等待才能继续执行 Ruby 代码。同样，Ruby 垃圾回收器是 
"stop-the-world"（停止 - 世界），因此当它触发时，所有线程都必须停止。&lt;/p&gt;

&lt;p&gt;这也意味着，无论 一个 Ruby 进程无论包含多少线程，它使用 CPU 内核都不会超过一个。&lt;/p&gt;

&lt;p&gt;因此，如果应用程序有 50% 时间在进行 I/O 操作，那么在每个进程中使用超过 2 或 3 
个线程可能会严重影响延迟，吞吐量方面收益也会迅速减少。&lt;/p&gt;

&lt;p&gt;一般来说，一个精心设计 Rails 应用程序不会因为 SQL 查询速度慢或 N+1 问题而导致 50% 以上时间用于 I/O 操作，
因此不太可能从 3 个以上线程中获益。不过，一些应用程序在内联时调用第三方 API，可能会花费很大一部分时间进行 I/O 
操作，因此可能会受益于更多线程。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用 Ruby 实现真正`并发`方法是使用多进程。代价是消耗更多内存。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要有一个空闲 CPU 内核，Ruby 进程在 I/O 操作完成后恢复执行之前就不必互相等待。不过，进程只能通过写入时复制共享部分内存，
因此一个额外进程使用内存比一个额外线程使用内存还要多。
需要注意是，虽然线程比进程便宜，但它们不是免费，增加每个进程线程数也会增加内存使用量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;因此，假如服务器有8Core，可以设置并发进程数为：`(8-2)*1.5=9`（留2Core给系统服务），每个进程线程数设置为 1。
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.2 “精兵简政”"&gt;2.2“精兵简政”&lt;/h3&gt;
&lt;p&gt;希望优化吞吐量和服务器利用率用户会希望每个 CPU 内核运行一个进程，并增加每个进程线程数，直到认为对延迟影响过于重要为止。
（榨干服务器性能）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;有意优化延迟用户应尽量减少每个进程线程数。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了进一步优化延迟，用户甚至可以将每个进程线程数设置为 1，并在每个 CPU 内核运行 1.5 或 1.3 个进程，
以考虑到进程空闲等待 I/O 操作时情况。需要注意是，有些托管解决方案可能只为每个 CPU 内核提供相对较少内存（RAM），
因此无法运行尽可能多进程来使用所有 CPU 内核。不过，大多数托管解决方案都有不同内存和 CPU 比例计划。&lt;/p&gt;

&lt;p&gt;另一个需要考虑问题是，由于 "写入时复制"（&lt;a href="https://en.wikipedia.org/wiki/Copy-on-write" rel="nofollow" target="_blank" title=""&gt;copy-on-write&lt;/a&gt;），Ruby 内存使用得益于规模经济效益。2 台 32 进程服务器比 16 台 4 进程服务器使用进程更少。（2×32 &amp;lt; 16x4）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;在总CPU核心数量一定情况下，应该选用单机更强（核心更多）少量服务器。`“精兵简政”`
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3 配置"&gt;3 配置&lt;/h2&gt;&lt;h3 id="3.1 Puma"&gt;3.1 Puma&lt;/h3&gt;
&lt;p&gt;Puma 配置位于 config/puma.rb 文件中。两个最重要 Puma 配置是每个进程线程数和进程数，Puma 称其为 workers。&lt;/p&gt;

&lt;p&gt;每个进程线程数通过 thread 指令配置。在默认生成配置中，它被设置为 3。可以通过设置 RAILS_MAX_THREADS 环境变量或直接编辑配置文件来修改它。&lt;/p&gt;

&lt;p&gt;进程数由 workers 指令配置。如果每个进程使用多个线程，则应将其设置为服务器上可用 CPU 内核数，
如果服务器正在运行多个应用程序，则应将其设置为希望应用程序使用内核数。如果每个工作者只使用一个线程，
那么可以将其增加到每个进程一个以上，以考虑工作者空闲等待 I/O 操作时情况。&lt;/p&gt;

&lt;p&gt;可以通过设置 WEB_CONCURRENCY 环境变量来配置 Puma Worker 数量。&lt;/p&gt;

&lt;p&gt;根据公式，如果使用的 Linux，借助&lt;code&gt;nproc&lt;/code&gt;命令：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/puma.rb&lt;/span&gt;
&lt;span class="n"&gt;threads_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"RAILS_MAX_THREADS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="n"&gt;threads_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threads_count&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="sb"&gt;`nproc`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.2 YJIT"&gt;3.2 YJIT&lt;/h3&gt;
&lt;p&gt;最近的 Ruby 版本带有一个名为 YJIT 的即时编译器。
无需赘述太多细节，JIT 编译器可以更快地执行代码，但会占用更多内存。除非真的无法承受额外的内存占用，否则强烈建议启用 YJIT。
对于 Rails 7.2，如果应用程序运行在 Ruby 3.3 或更高版本上，Rails 默认会自动启用 YJIT。旧版本的 Rails 或 Ruby 则需要手动启用，具体方法请参阅 &lt;a href="https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md" rel="nofollow" target="_blank" title=""&gt;YJIT documentation&lt;/a&gt; 。&lt;/p&gt;

&lt;p&gt;如果额外的内存使用是一个问题，在完全禁用 YJIT 之前，可以尝试通过 &lt;a href="https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md#decreasing---yjit-exec-mem-size" rel="nofollow" target="_blank" title=""&gt;--yjit-exec-mem-size&lt;/a&gt; 配置来调整它，使其使用更少的内存。&lt;/p&gt;
&lt;h3 id="3.3 内存分配器和配置"&gt;3.3 内存分配器和配置&lt;/h3&gt;
&lt;p&gt;由于大多数 Linux 发行版的默认内存分配器的工作方式，使用多线程运行 Puma 
可能会因&lt;a href="https://en.wikipedia.org/wiki/Fragmentation_(computing)" rel="nofollow" target="_blank" title=""&gt;内存碎片&lt;/a&gt;导致内存使用量意外增加。反过来，增加的内存使用量可能会导致应用程序无法充分利用服务器 CPU 内核。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[内存碎片](https://en.wikipedia.org/wiki/Fragmentation_(computing))也是为什么要减少线程数的原因之一。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了缓解这个问题，强烈建议将 Ruby 配置为使用另一种内存分配器：jemalloc。
Rails 生成的默认 Dockerfile 已预置安装和使用 jemalloc。但如果托管解决方案不是基于 Docker 的，就应该研究如何在那里安装并启用 jemalloc。&lt;/p&gt;

&lt;p&gt;如果出于某种原因无法做到这一点，另一种效率较低的方法是在环境中设置 MALLOC_ARENA_MAX=2 
，以减少内存碎片的方式配置默认分配器。但请注意，这可能会让 Ruby 运行得更慢，因此 jemalloc 是首选的解决方案。&lt;/p&gt;

&lt;p&gt;安装配置&lt;code&gt;jemalloc&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;autoconf libxslt-dev xsltproc docbook-xsl
git clone https://github.com/jemalloc/jemalloc.git
&lt;span class="nb"&gt;cd &lt;/span&gt;jemalloc
autoconf
./configure
make dist
&lt;span class="nb"&gt;sudo &lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者 Ubuntu 直接：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libjemalloc2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新编译 Ruby(换成&lt;code&gt;jemalloc&lt;/code&gt;)：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mise uninstall ruby@3.3.6
&lt;span class="nv"&gt;RUBY_CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'--with-jemalloc'&lt;/span&gt; mise &lt;span class="nb"&gt;install &lt;/span&gt;ruby@3.3.6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LD_PRELOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/lib/libjemalloc.so puma
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证 puma 服务器已经使用&lt;code&gt;jemalloc&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ps -ef | grep puma
ldd /proc/35650/exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/bbf92a72-2696-4261-834c-75a73b541f39.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;去 Google 搜索，大多使用 jemalloc 的应用内存使用率节省 80% 左右。&lt;/p&gt;
&lt;h2 id="4 性能测试"&gt;4 性能测试&lt;/h2&gt;
&lt;p&gt;由于每个 Rails 应用程序都不尽相同，每个 Rails 用户可能都想针对不同的属性进行优化，因此不可能提供最适合每个人的默认配置或指南。&lt;/p&gt;

&lt;p&gt;因此，选择应用程序设置的最佳方法是测量应用程序的性能，然后调整配置，直到达到满意为止。&lt;/p&gt;

&lt;p&gt;这可以通过模拟生产工作负载或直接在生产中使用实时应用流量来完成。&lt;/p&gt;

&lt;p&gt;性能测试是一门很深的学问。本指南仅提供简单的指导。&lt;/p&gt;
&lt;h3 id="4.1 测量什么？"&gt;4.1 测量什么？&lt;/h3&gt;
&lt;p&gt;吞吐量是指应用程序每秒成功处理的请求数。（俗称 RPS/TPS）任何好的负载测试程序都会测量它。吞吐量通常是以 "每秒请求数 "为单位的单一数字。&lt;/p&gt;

&lt;p&gt;延迟是指从发送请求到成功接收响应的延迟时间，通常以毫秒为单位。每个请求都有自己的延迟时间。&lt;/p&gt;

&lt;p&gt;百分比延迟给出的是某一部分的延迟更好。例如，P90 是 90% 以上的请求平均延迟。其中只有 10% 的请求处理时间长于该值。
 P50 是一半请求的平均延迟，也称为延迟中位数。&lt;/p&gt;

&lt;p&gt;"尾延迟 "是指高百分位数延迟。例如，P99 是指只有 1% 的请求的延迟较差。P99 是尾部延迟。P50 不是尾延迟。&lt;/p&gt;

&lt;p&gt;一般来说，平均延迟并不是优化的好指标。最好关注中位延迟（P50）和尾部延迟（P95 或 P99）。&lt;/p&gt;
&lt;h3 id="4.2 生产测试"&gt;4.2 生产测试&lt;/h3&gt;
&lt;p&gt;如果生产环境包含不止一台服务器，那么在这里进行 A/B 测试是个不错的主意。例如，可以让一半的服务器每个进程使用 3 个线程，另一半服务器每个进程使用 4 个线程，然后使用应用程序性能监控服务来比较两组服务器的吞吐量和延迟。&lt;/p&gt;

&lt;p&gt;应用程序性能监控服务有很多，有些是自托管的，有些是云解决方案，还有很多提供免费的分层计划。推荐特定的服务超出了本指南的范围。&lt;/p&gt;
&lt;h3 id="4.3 负载测试仪"&gt;4.3 负载测试仪&lt;/h3&gt;
&lt;p&gt;需要一个负载测试程序来向应用程序发出请求。这可以是某种专门的负载测试程序，或者也可以编写一个小程序来发出 HTTP 
请求并跟踪它们所需的时间。通常情况下，不应该检查 Rails 日志文件中的时间。该时间仅指 Rails 
处理请求所花费的时间。它不包括应用服务器花费的时间。&lt;/p&gt;

&lt;p&gt;同时发送多个请求并对其进行计时可能很困难。这很容易引入微妙的测量误差。通常情况下，应该使用负载测试程序，而不是自己编写。
许多负载测试程序使用简单，而且许多优秀的负载测试程序都是免费的。&lt;/p&gt;

&lt;p&gt;例如：loadrunner&lt;/p&gt;
&lt;h3 id="4.4 可以更改的内容"&gt;4.4 可以更改的内容&lt;/h3&gt;
&lt;p&gt;可以更改测试中的线程数，以便在吞吐量和延迟之间找到最适合应用的平衡点。&lt;/p&gt;

&lt;p&gt;拥有更多内存和 CPU 内核的大型主机需要更多进程才能达到最佳使用效果。可以改变主机提供商提供的主机的大小和类型。&lt;/p&gt;

&lt;p&gt;增加迭代次数通常会得到更准确的答案，但需要更长的测试时间。&lt;/p&gt;

&lt;p&gt;应该在与生产中运行的主机类型相同的主机上进行测试。在开发机器上进行测试只会告诉哪些设置最适合该开发机器。&lt;/p&gt;
&lt;h3 id="4.5 热机"&gt;4.5 热机&lt;/h3&gt;
&lt;p&gt;应用程序启动后应处理一些请求，这些请求不包括在最终测量中。这些请求称为 "预热 "请求，通常比后面的 "稳态 "请求慢得多。
负载测试程序通常支持预热请求。也可以多次运行，并丢弃第一组次数。
当增加预热请求的次数不会明显改变结果时，就有足够的预热请求了。这背后的理论可能很复杂，
但大多数常见情况都很简单：用不同的预热次数进行多次测试。看看需要进行多少次预热迭代，结果才会大致相同。
长时间预热有助于测试内存碎片和其他只有在多次请求后才会出现的问题。&lt;/p&gt;
&lt;h3 id="4.6 测试哪些请求？"&gt;4.6 测试哪些请求？&lt;/h3&gt;
&lt;p&gt;应用程序可能会接受许多不同的 HTTP 请求。一开始，只需对其中几种请求进行负载测试。
随着时间的推移，可以添加更多类型的请求。如果某类请求在生产应用程序中速度太慢，可以将其添加到负载测试代码中。
合成工作负载无法完全匹配应用程序的生产流量。但它仍然有助于测试配置。&lt;/p&gt;
&lt;h3 id="4.7 注意事项"&gt;4.7 注意事项&lt;/h3&gt;
&lt;p&gt;负载测试程序应允许检查延迟，包括百分位延迟和尾部延迟。&lt;/p&gt;

&lt;p&gt;对于不同数量的进程和线程，或一般的不同配置，检查吞吐量和一个或多个延迟，如 P50、P90 和 P99。增加线程会在一定程度上提高吞吐量，但会使延迟恶化。&lt;/p&gt;

&lt;p&gt;根据应用需求，在延迟和吞吐量之间做出权衡。&lt;/p&gt;</description>
      <author>memorycancel</author>
      <pubDate>Tue, 14 Jan 2025 13:49:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/44019</link>
      <guid>https://ruby-china.org/topics/44019</guid>
    </item>
    <item>
      <title>使用 Rails 8 提供的默认任务队列 Solid Queue</title>
      <description>&lt;h2 id="1 Solid Queue 简介"&gt;1 Solid Queue 简介&lt;/h2&gt;
&lt;p&gt;Solid Queue 是 Active Job 的一个基于数据库的队列后端，设计时考虑到了简单性和性能。&lt;/p&gt;

&lt;p&gt;它摒弃了&lt;code&gt;Redis&lt;/code&gt;内存数据库作为任务队列的后端存储（如：&lt;code&gt;sidekiq&lt;/code&gt;等）。&lt;/p&gt;

&lt;p&gt;可与 SQL 数据库（如 MySQL、PostgreSQL 或 SQLite）一起使用，它利用 &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;子句（如果有的话）来避免轮询作业时的阻塞和锁等待。它依赖 Active Job 进行重试、丢弃、错误处理、序列化或延迟执行，并与 Ruby on Rails 的多线程兼容。&lt;/p&gt;

&lt;p&gt;除了常规的作业排队和处理外，Solid Queue 还支持延迟作业、并发控制、重复作业、暂停队列、每个作业的数字优先级、队列顺序优先级和批量排队（enqueue_all for Active Job's perform_all_later）。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;这概念是否似曾相识，与Unix的进程控制很像？！通过在命令后加上`&amp;amp;`将任务传送至后台，在通过`jobs -l`查看后台任务队列，并且可以通过
`bg`和`fg`实现前后台操作。只不过在这里的Rails的Active Job 将其封装为一个线程，与操作系统的进程接口交互。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考官方文档：&lt;a href="https://guides.rubyonrails.org/v8.1/active_job_basics.html" rel="nofollow" target="_blank" title=""&gt;https://guides.rubyonrails.org/v8.1/active_job_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="2 环境安装和启动任务队列"&gt;2 环境安装和启动任务队列&lt;/h2&gt;
&lt;p&gt;Solid Queue 将 &lt;code&gt;:solid_queue&lt;/code&gt; 适配器设置为生产环境中 Active Job 的默认适配器，并连接到 queue 数据库进行写入。在开发环境中使用生产环境变量：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export RAILS_ENV=production
rails db:prepare
bin/jobs start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为啥要用生产环境？因为 Rails 官方没有准备测试和开发环境，似乎也没有必要。执行&lt;code&gt;rails db:prepare&lt;/code&gt;后会创建 solid_queue 需要的&lt;code&gt;queue_schema&lt;/code&gt;和创建对应的数据库表。最后启动 jobs 后台任务。&lt;/p&gt;

&lt;p&gt;此时在 db 文件夹会看到分别的&lt;code&gt;schema&lt;/code&gt;文件：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="se"&gt;\l&lt;/span&gt;s db/
cable_schema.rb  cache_schema.rb  queue_schema.rb  seeds.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且&lt;code&gt;Solid Queue&lt;/code&gt;对应的表在数据库中也建立起来：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/557c8f67-3143-434c-bf0c-f284244c8f3e.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="3 创建和发布任务"&gt;3 创建和发布任务&lt;/h2&gt;&lt;h3 id="3.1 创建作业"&gt;3.1 创建作业&lt;/h3&gt;
&lt;p&gt;创建一个在特定队列 (&lt;code&gt;bar_queue&lt;/code&gt;) 上运行的作业 (&lt;code&gt;foo_job&lt;/code&gt;)。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;foo_job&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="n"&gt;bar_queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时会创建空的&lt;code&gt;jobs/foo_job.rb&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;FooJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:bar_queue&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="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="c1"&gt;# Do something later&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;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;"slept &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&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="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds job!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;&lt;h3 id="3.2 让任务进入队列"&gt;3.2 让任务进入队列&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails c
FooJob.perform_now&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# 创建1个等待1周执行的任务&lt;/span&gt;
FooJob.set&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;wait&lt;/span&gt;: 1.week&lt;span class="o"&gt;)&lt;/span&gt;.perform_later&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时观察&lt;code&gt;solid_queue_jobs&lt;/code&gt;表会生成 1 条记录：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/03c32aa6-f48c-4a1d-b438-246ff2695c32.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;使用&lt;code&gt;set&lt;/code&gt;方法设置等待时间：&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveJob/Core/ClassMethods.html#method-i-set" rel="nofollow" target="_blank" title=""&gt;https://api.rubyonrails.org/v8.1.1/classes/ActiveJob/Core/ClassMethods.html#method-i-set&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;其他更多用法 (例如：设置优先级、回调方法、批量任务入队、邮件任务等) 参考官方文档：&lt;a href="https://guides.rubyonrails.org/v8.1/active_job_basics.html" rel="nofollow" target="_blank" title=""&gt;https://guides.rubyonrails.org/v8.1/active_job_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="4 任务（jobs)监控"&gt;4 任务（jobs) 监控&lt;/h2&gt;
&lt;p&gt;关于任务队列的监控，目前一种解决方案是通过查询 queue 数据库的&lt;code&gt;finished_at&lt;/code&gt; 和 &lt;code&gt;scheduled_at&lt;/code&gt;
字段，这将包括在队列中等待的时间。参考：&lt;a href="https://github.com/rails/solid_queue/issues/448" rel="nofollow" target="_blank" title=""&gt;https://github.com/rails/solid_queue/issues/448&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同时对任务队列的监控也提供一个 GUI：&lt;a href="https://github.com/rails/mission_control-jobs" rel="nofollow" target="_blank" title=""&gt;https://github.com/rails/mission_control-jobs&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="s2"&gt;"propshaft"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install
&lt;/span&gt;&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production rails assets:precompile
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bin/rails mission_control:jobs:authentication:configure
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production rails s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/31691dee-d054-4aa9-b21f-2448249c8620.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="5 关于性能扩展数据库分片"&gt;5 关于性能扩展数据库分片&lt;/h2&gt;
&lt;p&gt;任务队列的本质是不断的轮询数据库和少量写入，随着队列任务增多，数据库读的压力会骤增，简单对机器进行垂直扩展（CPU RAM）
起不到本质改变，需要水平扩展数据库分片（shard），从而提升“读”（轮询）效率。
目前 Solid Queue 还没完备支持。不过已经有 Issue 在追踪此问题，作者也将其列为高优先级。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/solid_queue/issues/353" rel="nofollow" target="_blank" title=""&gt;https://github.com/rails/solid_queue/issues/353&lt;/a&gt;&lt;/p&gt;</description>
      <author>memorycancel</author>
      <pubDate>Fri, 03 Jan 2025 18:13:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/44002</link>
      <guid>https://ruby-china.org/topics/44002</guid>
    </item>
    <item>
      <title>使用 Github Primer Design System 设计和 Github 风格一致的布局</title>
      <description>&lt;p&gt;&lt;a href="https://primer.style/" rel="nofollow" target="_blank" title=""&gt;primer&lt;/a&gt; 是 Github 开源设计框架，目的是对 github 设计经验进行输出。
极具设计美感，且对移动端、rails 生态天然友好。
先准备好环境：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# v23.5.0&lt;/span&gt;
ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [x86_64-linux]&lt;/span&gt;
rails &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# Rails 8.0.1&lt;/span&gt;
mise &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# 2024.10.7 linux-x64 (7d15bd5 2024-10-14)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建项目。并且引入 Primer，参考步骤：&lt;a href="https://primer.style/guides/rails" rel="nofollow" target="_blank" title=""&gt;https://primer.style/guides/rails&lt;/a&gt;
首先在 Gemfile 引入&lt;a href="https://rubygems.org/gems/primer_view_components" rel="nofollow" target="_blank" title=""&gt;https://rubygems.org/gems/primer_view_components&lt;/a&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'primer_view_components', '~&amp;gt; 0.36.2'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt;
mise &lt;span class="nb"&gt;install &lt;/span&gt;nodejs
mise use &lt;span class="nt"&gt;-g&lt;/span&gt; node@23.5.0
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn
yarn init
yarn add @primer/view-components @primer/css @primer/primitives
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 config/application.rb 的最下方添加以下几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"view_component"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"primer/view_components"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为&lt;code&gt;rails v 8.0.0&lt;/code&gt;默认使用&lt;code&gt;Propshaft&lt;/code&gt;作为前端管理框架，在 config/application.rb 中添加以下内容，将 node_modules/ 目录添加到 Propshaft 的加载路径中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 app/views/layouts/application.html.erb 中的 &lt;/p&gt; 标记内添加以下内容。
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= stylesheet_link_tag "@primer/css/dist/primer.css", "data-turbo-track": "reload" %&amp;gt;
&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt; &lt;span class="s2"&gt;"@primer/view-components/app/assets/styles/primer_view_components.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"reload"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= stylesheet_link_tag "@primer/primitives/dist/css/primitives.css", "data-turbo-track": "reload" %&amp;gt;
&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt; &lt;span class="s2"&gt;"@primer/primitives/dist/css/functional/themes/light.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"reload"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= stylesheet_link_tag "@primer/primitives/dist/css/functional/themes/dark.css", "data-turbo-track": "reload" %&amp;gt;
&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"@primer/view-components/app/assets/javascripts/primer_view_components.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"reload"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建一个主页 controller：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails g controller main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;main_controller.rb&lt;/code&gt; 添加 page action&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建一个页面&lt;code&gt;views/main/page.html.erb&lt;/code&gt;。添加一个布局，参考&lt;a href="https://primer.style/components/layout/rails/alpha" rel="nofollow" target="_blank" title=""&gt;https://primer.style/components/layout/rails/alpha&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :md, gutter: :default, first_in_source: :main)) do |component| %&amp;gt;
  &amp;lt;% component.with_main(bg: :attention, p: 6) do %&amp;gt;
    &amp;lt;div style=&lt;/span&gt;&lt;span class="s2"&gt;"background-color: red;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Main&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% component.with_sidebar(bg: &lt;/span&gt;&lt;span class="ss"&gt;:accent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;p: &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
    &amp;lt;div style="background-color: green;"&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Sidebar&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里，一个移动端兼容的响应式布局就完成了。
&lt;img src="https://l.ruby-china.com/photo/memorycancel/ee19a2c7-9dd8-4531-8c47-8637a3f4acd4.gif!large" title="" alt=""&gt;
我们再将 sidebar 变成一个菜单导航列表，参考&lt;a href="https://primer.style/components/nav-list" rel="nofollow" target="_blank" title=""&gt;https://primer.style/components/nav-list&lt;/a&gt;
首先将上部分 &lt;code&gt;&amp;lt;div style="background-color: green;"&amp;gt;&amp;lt;h1&amp;gt;Sidebar&amp;lt;/h1&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; 替换为一个&lt;code&gt;patial&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :md, gutter: :default, first_in_source: :main)) do |component| %&amp;gt;
  &amp;lt;% component.with_main(bg: :attention, p: 6) do %&amp;gt;
    &amp;lt;div style=&lt;/span&gt;&lt;span class="s2"&gt;"background-color: red;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Main&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% component.with_sidebar(bg: &lt;/span&gt;&lt;span class="ss"&gt;:accent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;p: &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
    &amp;lt;%= render 'sidebar_nav' %&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑&lt;code&gt;_sidebar_nav.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= render(Primer::Beta::NavList.new(selected_item_id: :collaborators)) do |list| %&amp;gt;
  &amp;lt;% list.with_heading(title: "Repository settings") %&amp;gt;
  &amp;lt;% list.with_item(label: "General", href: "/general") do |item| %&amp;gt;
    &amp;lt;% item.with_leading_visual_icon(icon: :gear) %&amp;gt;
  &amp;lt;% end %&amp;gt;
  &amp;lt;% list.with_group do |group| %&amp;gt;
    &amp;lt;% group.with_heading(title: "Access") %&amp;gt;
    &amp;lt;% group.with_item(label: "Collaborators and teams", href: "/collaborators", selected_by_ids: :collaborators) do |item| %&amp;gt;
      &amp;lt;% item.with_leading_visual_icon(icon: :people) %&amp;gt;
    &amp;lt;% end %&amp;gt;
    &amp;lt;% group.with_item(label: "Moderation options") do |item| %&amp;gt;
      &amp;lt;% item.with_leading_visual_icon(icon: :"comment-discussion") %&amp;gt;
      &amp;lt;% item.with_item(label: "Interaction limits", href: "/interaction-limits", selected_by_ids: :interaction_limits) %&amp;gt;
      &amp;lt;% item.with_item(label: "Code review limits", href: "/review-limits", selected_by_ids: :code_review_limits) %&amp;gt;
      &amp;lt;% item.with_item(label: "Reported content", href: "/reported-content", selected_by_ids: :reported_content) %&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/707e5992-34e5-4d5f-a362-298ff467910c.gif!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样我们就可以做一个和 Github 风格一致的网站了，是不是很酷，嗯？&lt;/p&gt;</description>
      <author>memorycancel</author>
      <pubDate>Wed, 01 Jan 2025 20:04:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/43997</link>
      <guid>https://ruby-china.org/topics/43997</guid>
    </item>
    <item>
      <title>浅谈 Rails 8.0.0 特性</title>
      <description>&lt;h2 id="浅谈 Rails 8.0.0 特性"&gt;浅谈 Rails 8.0.0 特性&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Rails 8.0.0&lt;/code&gt;  版本基于 &lt;code&gt;Ruby 3.3.5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;分几个部分介绍：开发、中间件、部署。&lt;/p&gt;
&lt;h2 id="一、开发"&gt;一、开发&lt;/h2&gt;&lt;h3 id="1.1 使用开发模式启动rails服务器"&gt;1.1 使用开发模式启动 rails 服务器&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1.2 脚手架/资源"&gt;1.2 脚手架/资源&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate scaffold post title:string body:text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会生成全部 MVC，DB migration，以及 spec 测试文件。
如果不需要 View 可以使用：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails g resource comment post:references comment:string
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1.3 通过CDN的方式快速引入CSS"&gt;1.3 通过&lt;code&gt;CDN&lt;/code&gt;的方式快速引入&lt;code&gt;CSS&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;编辑&lt;code&gt;app/views/layouts/application.html.erb&lt;/code&gt;，插入一行&lt;code&gt;&amp;lt;%= stylesheet_link_tag "https://cdn.simplecss.org/simple.css" %&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%# Includes all stylesheet files in app/assets/stylesheets %&amp;gt;
    &amp;lt;%= stylesheet_link_tag :app, "data-turbo-track": "reload" %&amp;gt;
    &amp;lt;%= stylesheet_link_tag "https://cdn.simplecss.org/simple.css" %&amp;gt;
    &amp;lt;%= javascript_importmap_tags %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1.4 方便的前端console debug功能"&gt;1.4 方便的前端 console debug 功能&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[ show edit update destroy ]&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /posts or /posts.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'err'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/2a6d40e1-d965-4356-a67c-b9210496dfd8.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="1.5 一行代码创建富文本编辑"&gt;1.5 一行代码创建富文本编辑&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails action_text:install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;model&lt;/code&gt;，添加对应富文本字段&lt;code&gt;has_rich_text :body&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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="n"&gt;has_rich_text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;view&lt;/code&gt;，将&lt;code&gt;textarea&lt;/code&gt;修改为&lt;code&gt;rich_textarea&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= form.label :body, style: "display: block" %&amp;gt;
    &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rich_textarea&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/8608f614-2ada-4ce2-b881-77ccea8cc0ad.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="1.6 使用基于websocket的hotwired功能，让页面数据实时更新："&gt;1.6 使用基于&lt;code&gt;websocket&lt;/code&gt;的&lt;code&gt;hotwired&lt;/code&gt;功能，让页面数据&lt;code&gt;实时&lt;/code&gt;更新：&lt;/h3&gt;
&lt;p&gt;修改&lt;code&gt;View&lt;/code&gt;在页面添加&lt;code&gt;turbo_stream_from&lt;/code&gt;标签：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_stream_from @post %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;model&lt;/code&gt;添加&lt;code&gt;broadcasts_to&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;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;
  &lt;span class="n"&gt;broadcasts_to&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://tommytech.online/bring-it/assets/images/2024-11-20/3.gif" title="" alt="3"&gt;
此时我们同时打开 2 个网页，在一个网页上更新一条数据，那么在另外 1 个网页页面会实时更新，
而不用按下“F5”。&lt;/p&gt;

&lt;p&gt;将鼠标移到链接上，从日志可以看到接收到了&lt;code&gt;get&lt;/code&gt;请求，为什么页面跳转会变快，
从这里也“可见一斑”。&lt;/p&gt;

&lt;p&gt;也就是说当鼠标移到链接上（还没有点击时），服务器已经开始处理请求了。
这会大大提高客户端的使用体验，服务器也会增加一定的负担。&lt;/p&gt;
&lt;h3 id="1.7 内置身份验证"&gt;1.7 内置身份验证&lt;/h3&gt;
&lt;p&gt;只需运行一条命令就可完成登陆功能：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate authentication
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;application.html.erb&lt;/code&gt;加入退出登陆：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;%= button_to "sign out", session_path, method: :delete if authenticated?  %&amp;gt;
    &amp;lt;%= yield %&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1.8 脚本文件管理"&gt;1.8 脚本文件管理&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails generate script my_script
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;ruby script/my_script.rb
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="二、中间件"&gt;二、中间件&lt;/h2&gt;&lt;h3 id="2.1 后端中间件"&gt;2.1 后端中间件&lt;/h3&gt;
&lt;p&gt;官方原话是 Solid Adapters（可靠的适配器）。在国内我们喜欢用“中间件”这一词。
主要有 3 部分：&lt;code&gt;Solid Cable&lt;/code&gt;、&lt;code&gt;Solid Cache&lt;/code&gt;、&lt;code&gt;Solid Queue&lt;/code&gt;。
&lt;code&gt;rails 8&lt;/code&gt; 放弃了对&lt;code&gt;redis&lt;/code&gt;的依赖。取而代之使用&lt;code&gt;SQLite&lt;/code&gt;和 3 个中间件融合解决了&lt;code&gt;pub/sub&lt;/code&gt; websocket 服务器、
缓存、以及队列 3 个重要功能。&lt;/p&gt;

&lt;p&gt;这三个适配器的设计理念很简单：现代固态硬盘和 NVMe 硬盘的速度足以处理许多以前需要内存解决方案才能完成的任务。通过利用这些高速驱动器，Rails 不再需要单独的基于 RAM 的工具，如 Redis。&lt;/p&gt;
&lt;h3 id="2.2 前端中间件"&gt;2.2 前端中间件&lt;/h3&gt;
&lt;p&gt;Rails 8 还引入了 &lt;code&gt;Propshaft&lt;/code&gt;作为新的 assets pipeline 默认设置，取代了沿用已久的 &lt;code&gt;Sprockets&lt;/code&gt; 。它是一种更简单、更现代的 assets 管理方法，围绕当今开发人员的核心需求而构建的。它的目的很简单：为 assets 提供清晰的路径，并应用 digest 进行缓存。&lt;/p&gt;
&lt;h2 id="三、部署"&gt;三、部署&lt;/h2&gt;
&lt;p&gt;部署方面 rails8 默认加入了&lt;code&gt;Kamal 2&lt;/code&gt; 和 &lt;code&gt;Thruster&lt;/code&gt;，分别解决了 docker 容器化部署
以及&lt;code&gt;https&lt;/code&gt;自动证书问题。
但国内目前并没有与之适配基础设施，慎用。
如果需要跳过&lt;code&gt;kamal&lt;/code&gt;执行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new blog --skip-kamal
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="参考："&gt;参考：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://guides.rubyonrails.org/v8.0/" rel="nofollow" target="_blank" title=""&gt;https://guides.rubyonrails.org/v8.0/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.appsignal.com/2024/10/07/whats-new-in-ruby-on-rails-8.html" rel="nofollow" target="_blank" title=""&gt;https://blog.appsignal.com/2024/10/07/whats-new-in-ruby-on-rails-8.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://edgeguides.rubyonrails.org/8_0_release_notes.html" rel="nofollow" target="_blank" title=""&gt;https://edgeguides.rubyonrails.org/8_0_release_notes.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>memorycancel</author>
      <pubDate>Wed, 20 Nov 2024 19:21:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/43956</link>
      <guid>https://ruby-china.org/topics/43956</guid>
    </item>
    <item>
      <title>尝鲜 Omakase</title>
      <description>&lt;p&gt;&lt;code&gt;Omakase(おまかせ)是日语词汇,在美食界通常指“厨师发办”,即客人无需点菜,完全由主厨根据当季食材、个人喜好和餐厅特色来安排菜品。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Omakase 是 DHH 最新花活，见 &lt;a href="https://omakub.org/" rel="nofollow" target="_blank"&gt;https://omakub.org/&lt;/a&gt;  ，旨在 Ubuntu24.04 系统上，搭建统一的 Rails 开发环境。
刚好家里有一台 Dell 工作站，果断装之，甚好。以下是安装时大致步骤记录和分享之。
Omakase 遵循“默认大于配置”原则，甚至有推荐的笔记本 &lt;a href="https://frame.work/" rel="nofollow" target="_blank"&gt;https://frame.work/&lt;/a&gt; ，我用 Dell 工作站高替。在这里我只选取了大部分喜欢的“菜”浅尝，并非尝完全部。&lt;/p&gt;
&lt;h2 id="步骤一 安装ubuntu"&gt;步骤一 安装 ubuntu&lt;/h2&gt;
&lt;p&gt;找一台可以装 windows 的电脑，下载 UltraISO 软件和 &lt;a href="https://ubuntu.com/download/desktop#system-requirements" rel="nofollow" target="_blank" title=""&gt;Ubuntu 24.04 Desktop ISO 镜像文件&lt;/a&gt;,准备一个 U 盘，将文件考入 U 盘，
重启电脑，F2 进入 Boot 修改启动项目，按照自动向导安装 Ubuntu。&lt;/p&gt;

&lt;p&gt;另外准备好科学上网，rust（cargo）具体步骤略。&lt;/p&gt;
&lt;h2 id="步骤二 Omakase“上菜”"&gt;步骤二 Omakase“上菜”&lt;/h2&gt;
&lt;p&gt;到这里相当于已经走进餐厅了，遂叫“厨师”&lt;a href="https://omakub.org/#installing-omakub" rel="nofollow" target="_blank"&gt;https://omakub.org/#installing-omakub&lt;/a&gt;，上菜。也就是执行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget -qO- https://omakub.org/install | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令，到这里其实并没有按照预想上完所有菜，因为“食材”（电脑、网络环境）都不一样。真是“巧妇难为无米之炊”啊～ 好在 DHH 主厨为我们准备了“菜谱”，我们只需要替换相应的事材，然后按照菜谱制作就好了。&lt;/p&gt;
&lt;h2 id="步骤三 亲自下厨"&gt;步骤三 亲自下厨&lt;/h2&gt;
&lt;p&gt;于是我挑选了几道我爱的精品菜。烹之。先准备“以下食材”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;书呆子 Nerd 自体 &lt;a href="https://www.nerdfonts.com/" rel="nofollow" target="_blank"&gt;https://www.nerdfonts.com/&lt;/a&gt; 有很多变体版本自选，装上后终端会养眼很多。&lt;/li&gt;
&lt;li&gt;高替 rvm &lt;a href="https://mise.jdx.dev/" rel="nofollow" target="_blank"&gt;https://mise.jdx.dev/&lt;/a&gt; ，rust 重写的，可以管理 ruby node 等等版本，包会比 rvm 新，还会自动检测项目里面的.ruby_version 切换版本&lt;/li&gt;
&lt;li&gt;高替 terminal &lt;a href="https://alacritty.org/" rel="nofollow" target="_blank"&gt;https://alacritty.org/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;高替 tmux  &lt;a href="https://zellij.dev/" rel="nofollow" target="_blank"&gt;https://zellij.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;高替 vi/vim &lt;a href="https://neovim.io/" rel="nofollow" target="_blank"&gt;https://neovim.io/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;（不得不说 rust 野心真大，重写很多 Linux 基础设施，感觉想大一统）&lt;/p&gt;

&lt;p&gt;安装自行参考官方链接，或者使用 rust（cargo）进行安装。&lt;/p&gt;
&lt;h2 id="制作“预制菜”"&gt;制作“预制菜”&lt;/h2&gt;
&lt;p&gt;mise,neovim 和字体安装好后就自动替换默认了，最后将 alacritty 设置为默认的 terminal&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; GNOME Keyboard Settings
        &amp;gt; "Customize Shortcuts"
            &amp;gt; "Custom Shortcuts"
                &amp;gt; "Add shortcuts"
                    - `alacritty -e zellij`
                    - Set keybinding to terminal launch (i.e. `Ctrl+Alt+T`)    

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 alacritty 设置为默认的 terminal&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cargo install zellij alacritty
$ sudo update-alternatives --install \
      /usr/bin/x-terminal-emulator x-terminal-emulator \
$(which alacritty) 50
$ sudo update-alternatives --config x-terminal-emulator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑 &lt;code&gt;~/.config/alacritty/alacritty.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shell:
    program: /usr/bin/bash
    args:
    - -l
    - -c
    - zellij attach --index 0 || zellij
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="收工"&gt;收工&lt;/h2&gt;
&lt;p&gt;至此，酒足饭饱，后面通过&lt;code&gt;Ctrl+Alt+T&lt;/code&gt;就可以打开 alacritty，通过 vi 打开文件夹进行 coding 了（具体食用方法参考&lt;a href="https://zellij.dev/tutorials/basic-functionality/" rel="nofollow" target="_blank"&gt;https://zellij.dev/tutorials/basic-functionality/&lt;/a&gt;），如下图&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/3713ee31-0724-4318-a72b-65c349651118.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="最后"&gt;最后&lt;/h2&gt;
&lt;p&gt;当然，对于&lt;code&gt;字体，包管理器，编辑器，终端&lt;/code&gt;都是众口难调、老生长谈的问题，面对新的工具，我们还是要发扬“拿来主义”精神，好则留，坏则废。
这也遵循 Rubyist 的 slogan，进步总是比稳定要好～&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/memorycancel/38abb0e7-fcb2-4175-a91c-ad4f3114cc16.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;DHH，霸总镇楼～&lt;/p&gt;</description>
      <author>memorycancel</author>
      <pubDate>Mon, 28 Oct 2024 14:46:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/43931</link>
      <guid>https://ruby-china.org/topics/43931</guid>
    </item>
  </channel>
</rss>
