• btw 我们用回滚来恢复服务的方式也很有意思,虽然 db 在回滚代码后就恢复了,但是真正起作用的不是代码回滚了,而是滚动部署时,旧 db connection 被断开了,释放了锁。

    阴差阳错,其实重启也能起到断开 db conn 的作用。重启和回滚,两大恢复服务的利器,哈哈😄

  • 突然想到,我们找解决方案一般都用错误日志/症状搜索。这次的错误日志 (lock error) 和症状(100%cpu、high timeout:spinlock ) 挺独特的,从这个角度写文章,或许也可以帮到网友 😄

  • 全栈营教程免费开放了 at 2023年02月09日

    好几年前就免费开放了~

  • Ruby 有什么好的教程吗? at 2022年10月13日

    学一门语言很大程度上是学它的 library 用法。如果你学 ruby 是为了写 web 应用,可以看这本五星好评的 rails 教程 https://www.railstutorial.org/

  • 换 good_job,频率限制也是开源的 https://github.com/bensheldon/good_job#activejob-concurrency

    good_job 是 Postgres-based 的异步任务框架,还是建议使用 sidekiq

    • sidekiq 基于 redis,redis 相比 pg,redis 运维成本低(问题少,至少不需要一个专门的 DBA)
    • redis 可以水平拓展读写能力,pg 只能水平扩展读
    • 相同配置下,redis 读写性能强很多
  • 让 sidekiq 任务一个一个执行

    把这句话换成串行,应该会更容易搜索答案。有两种方式

    • 【推荐】专门的 queue 来处理该 job,只在一个 worker 上消费该 queue,worker 的 concurrency 设为 1(楼上好几位都提到了)
    • 把 job 聚合起来,再另一个 job 按顺序执行(同步执行)。这个sidekiq-grouping gem 已经实现了该功能

    如果不同平台的 job 可以并行执行,那我们可以对该 queue 做个分区

    platform_name = 'A'
    queue = "platform" + "_" + hash_algorithm(platform_name) % 3
    MyJob.set(queue: queue).perform_async(params)
    
    # worker1
    :concurrency: 1
    :queues:
      - ["platform_0", 1]
    
    # worker2
    :concurrency: 1
    :queues:
      - ["platform_1", 1]
    
    # worker3
    :concurrency: 1
    :queues:
      - ["platform_2", 1]
    
  • 优雅地更新数据库索引 at 2022年09月30日

    涉及到索引的创建,当表数据量比较大时,一定要避开业务高峰期

    对于 PG,避开业务高峰的最大原因是 drop/create index 需要锁住整张表,会阻塞住读写操作。(文档)。如果在生产对一张核心表做这个操作,数据库读写会马上阻塞,然后 API 服务请求队列开始堆积,用户端立即无法响应(泪。。)

    最佳实践是使用 concurrently 选项,这样的话就不会阻塞读写操作 e.g.

    remove_index :notifications, name: :non_uniq_index_notifications_on_order_id, column: :order_id,
      algorithm: :concurrently
    

    强烈推荐使用 strong_migrations,可以防止数据库高风险动作,免费雇了一位 DBA 😌

  • 是的,多谢提醒,并发编程得时刻惦记着锁👍。但是对于几乎并发操作的场景,应该可以忽略吧

  • 如 1 楼分享的帖子,多线程应用在部署时要 graceful 地停止服务(停止处理新请求),就得用到线程间通讯。sidekiq 和 puma 都是 multi-threads 的并发实现模型,它们会做的比较优雅,不是直接简单地用全局变量,而是用一个 manager 的单例(single instance)来通知其他工作线程。

    Sidekiq

    • manager 这个单例专门管理其他工作线程:start, quite, stop
    • processor 工作线程,负责具体任务:从队列取 jobs,执行具体 job
  • 考的应该是线程间如何通信,有以下方式

    • IPC(inner process communication) 通信方式都适用于线程间通信,比如:文件、网络
    • 线程之间共享内存:全局变量
    # inner-thread communication by memory
    $status = :pending
    
    def done?
      $status == :done
    end
    
    def done!
      puts "done!"
      $status = :done
    end
    
    def main
      thread_a = Thread.new do
        loop do
          sleep(0.5)
    
          if rand(100) < 10
            done!
            puts 'exit thread_a'
            break
          end
        end
      end
    
      thread_c = Thread.new do
        loop do
          if done?
            puts 'exit thread_c'
            break
          end
          sleep(0.5)
          done! if rand(100) < 10
        end
      end
    
      threads = [thread_a, thread_c]
      threads.each(&:join)
    end
    main
    
  • Rails 应用的部署管理 at 2022年08月13日

    我以前也认为绝大部分项目都很小,拧拧螺丝就好了,没必要学这些造火箭知识。后来接触相对大规模的项目后,才发现这类知识真的挺有用。(因为你的代码能影响很多用户,所以觉得有用)如果你等到需要时再学,要么临时抱佛脚,要么机会让给别人。

    ruby 社区也有大项目的 💪

  • 十分宝贵的分账经验🙏。之前只是从研发的角度去看聚合支付,以为它仅仅是节省了研发成本,原来聚合支付提供的分账功能才是关键功能👍

  • 大三暑假时在佛山外包公司找到一份 Android 实习,做了几个月后刚好另一个 Ruby 后端实习生走了,我便有机会参与用 Ruby 写服务端。 因为我的大学专业不是计算机,在这段实习经历每天都学到很多新知识,以及非常基础、实用的知识:前后端工程化(搭建、部署)、数据库基本操作、linux 基本维护。。。

    外包公司呆了一段时间后,工作内容逐渐重复,之后去了广州做 Ruby 后端。不过接触到的项目仍然小项目,吞吐量不大,也没啥生产问题能解决,常常是一个项目上线,它的生命周期就结束了。 不过庆幸的是,还是能学到一些东西:其他 Ruby 项目是如何搭建、维护的,跟我以前的维护 ruby 项目有哪些不同。

    在即将厌倦当时那份工作时,前外包上司恰好内推我到深圳的 Letote。这是我觉得维护的第一个真正有意义的项目:被真实用户使用、业务有收入。这个感觉太好了!

    呆了一段时间后,工作内容又逐渐重复了,恰好前 Letote 同事内推我去深圳另一家 Fintech 公司做 Ruby 后端。这是我第一次维护一个超多用户、高吞吐量的项目。 金融类项目的稳定性要求远远高于其他项目,在处理问题时,学到了非常多知识:

    • 高稳定性的实践
    • 性能优化:数据库查询优化、Ruby 代码优化
    • 云资源成本优化
    • 线上问题排查
    • 英语口语也有点突破(起码能开口交流了,哈哈)
    • 。。。

    因为高吞吐量、稳定性要求高,在这些公司还有非常多问题待解决、待学习。

    回顾我这点经历,最值得庆幸的是每个地方我都能学到东西以及多谢前同事的内推~我仍在 Ruby 旅程上~

  • “哪些字段能让你快速过滤掉大量无关数据”

    换句话说,也就是这个查询条件的区分度足够高 (selectivity)。带有 unique index 的 column 区分度最高,因为表里每条记录的值都不一样,即使是有几百万行的表,通过 B Tree 索引,只需要几次 IO 就能找到对应的记录。高区分度 => 查询范围小 => 快 🚀

    通过 pg_stats 看各个字段的值的分布情况。比如一个枚举字段 status,各个值的出现频率是 ({done: 0.9, failed: 0.08, pending: 0.02}),那 status = pending 就是一个高区分度的查询条件,因为它能过滤掉 99.8% 的数据。

    你甚至可以给一个高区分度的查询条件建一个partial index,进一步减少查询范围

  • 一般每个编程语言或者流行框架都有一个 awesome list repo,可以在这些 list 找到流行、开源的项目。

    比如这个 awesome rails:https://github.com/gramantin/awesome-rails#open-source-rails-apps

  • 我也尝试了 $ sudo bundle lock --add-platform x86_64-linux

    执行这句命令后,你本地的 Gemfile.lock 会有改变。你有没有提交这个修改到 git 仓库并且推送到远端?如果你没把这修改送到远端,那你下一次使用 cap 部署时还是用着旧的 Gemfile.lock 文件,导致 PLATFORMS 不含 x86_64-linux

  • 15% VS Code + 85% RubyMine

    RubyMine 的优势

    • Debug 模式比命令行的 byebug 方便,比如:查看调用栈,各个栈的变量(局部、实例)一目了然,打断点方便很多
    • 方便查看 gem 的源码(不然得用 vs code 打开另一个项目去查看,差好几步操作)
    • 内建的代码跳转(code navigation),vs code 也行,但是得手动配,而且 vs code 无法跳转到 gem 源码

    VS Code 的优势

    • 比 Ruby Mine 轻:启动速度快、占内存少一点
    • 用啥编程语言开发都还不错
    • GitHub Copilot(呜呜,RubyMine 不能用 Copilot) 原来 ruby mine 也有 copilot 插件~
  • 因为 Rack 不支持 HTTP2,基于 Rack 的 Ruby Server 都无法直接使用 HTTP2。

    虽然 Rack 不支持,但是一般一个 HTTP Request 不是直接发送到 Rails Server(比如 puma)的,在 Rails Server 前面会有负载均衡器比如 Nginx、AWS Load Balances(ALB)。或者像 RubyChina 这样使用 Cloudflare。

    Nginx、ALB、CLoudflare 都支持 HTTP2。这样的话,用户到负载均衡器之间的 HTTP 请求就能使用 HTTP2 的功能了,比如复用 TCP 连接。

    user    ->    Load Balancer      ->    Rails
           http2                 http1.1
    
  • 感谢分享

  • 一个电商小程序,有一些分润和代理的逻辑,没有复杂的促销,单纯下单配送。

    请问大佬后端提供了多少个 API?有多少个 test case?

    3 天就能交付 C 端和 B 端(前后端),膜拜膜拜 😍

  • 我后悔“没有给 redis 配置 retry delay 时间,当 redis 响应时间抖动一下,所有 redis client 都立即重试,导致 redis cpu usage 涨至 +90% ”

  • 我后悔“没有配置 rack-timeout 的 wait_timeout,当 db 短暂地出现问题,导致所有 API 响应慢,puma 的 request queue 开始疯狂堆积,每个 API response time 越来越慢”

    db 恢复后,也只能通过重启所有 containers 来清理 request queue

  • 我后悔“没有给每条 SQL 加上 API 备注,当 DB 出现问题时,无法快速排查这些 top sql 或者 slow sql 来自哪个 API/Job”

    marginalia gem 可以给每条 sql 加备注,rails 7 就内建了~

  • 我后悔“打印日志时上下文信息太少了,无法排查问题”

    # bad
    logger.error("vendor API failed")
    
    # good
    logger.error({user_id: id, msg: "vendor API failed", response: santized_vendor_api_response})
    
  • RubyChina 应该也需要介绍工具的文章,这类文章第一步就是让读者能快速在本地运行工具,接着在折腾该工具的其他功能。

    我喜欢 rails tutorial 和 railscast 的原因就是它们提供了手把手教程,可以让我很快在本地把功能跑起来。

    PS

    的确可以删除常见命令的输出,比如:rails new demo, rails g scaffold, yarn install

  • 这两句话放到一起会不会好点?看到第一句话时我在想前面什么时候启动了 container,怎么拿到 container id。

    WebSocket 的地址中的 b6bb364f6d06 是 container 的 ID。

    .

    $ docker run --name test -d -it debian

  • 趁午休在本地把 webssh 跑起来了,玩了一下,很酷。

  • activerecord-tidb-adapter released at 2021年08月12日

    官方功能,看私人 repo“感觉”更加安全了。 在 repo 页面点击 .,就能进入 web 版 vs code。 体验了创建分支、改文件、提交、创建 pr、浏览 pr、评审 pr,十分流畅。更重要的是还支持 vs code 的快捷键!

    彩蛋:在 pr 页面也可以通过点击.进入 vs code 模式,评审大 pr 的时候实在是太方便了

  • activerecord-tidb-adapter released at 2021年08月12日

    现在 GitHub 的正确打开姿势是这个😁https://github.dev/pingcap/activerecord-tidb-adapter (官方)

  • 期待这些话题

    • 2021 年 Ruby 高并发编程指北
    • 如何有效完成 Rails 应用的全方面监控与疑难 BUG 处理
    • SaaS as a Module — Rails 业务组件化开发实践
    • 高可用,高可靠,高级查询的极狐 GitLab 实践
    • How to scale up Ruby on Rails
    • 跨时区 Ruby 远程团队的经验分享