• 还不是很理解,为啥 broadcast 的时候要用 thread pool,因为 io 已经 异步了

    ActionCable 的实现是在内存里维护一个订阅列表的 hash,key 是 channel,value 是一个订阅者回调 lambda 的 array,

    回调 lambda : https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/streams.rb#L146-L154

    def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
      handler = stream_handler(broadcasting, user_handler, coder: coder)
    
      -> message do
        connection.worker_pool.async_invoke handler, :call, message, connection: connection
      end
    end
    

    broadcast 的时候:https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb#L36-L45

    def broadcast(channel, message)
      list = @sync.synchronize do
        return if !@subscribers.key?(channel)
        @subscribers[channel].dup
      end
    
      list.each do |subscriber|
        invoke_callback(subscriber, message)
      end
    end
    

    invoke_callback最终是上面那段 lambda

    -> message do
      connection.worker_pool.async_invoke handler, :call, message, connection: connection
    end
    

    worker_pool是一个 thread pool (Concurrent::ThreadPoolExecutor),async_invoke是往 thread pool 里塞任务,任务异步执行,所以遍历 subscribers 时是不会阻塞的,而消息何时向全部 client 发完取决于 thread pool 消化任务是的速度了。而且整个 ActionCable 是公用一个 thread pool 的。

  • 可以看一下 https://hashrocket.com/blog/posts/websocket-shootout 粗略比较了各种编程语言/框架 websocket 的指标。

    结论

    When it comes to websockets, Clojure, Elixir, and Go are all choices that balance great performance with ease of development. NodeJS is in a lower performance tier, but is still good enough for many uses. Rails can only be recommended when part of an existing Rails application with few concurrent clients. C++ offers the best performance, but it is hard to recommend due to the complexity and difficulty of development.

    需要指出的是做测试的时候 Rails 6 刚出,puma 还没有用上 nio4r,所以 Rails 支持的连接数看起来弱爆了,但是不代表现在 Rails 的真实水平。

    ActionCable 比较大的问题是 broadcast 是基于线程池的,线程池大小默认是 5(可以通过 config.action_cable.worker_pool_size 调整),换句话说一个 puma worker 同一时间只能同时向 5 个 websocket client 发消息 😂

  • 装饰器可以对原方法进行修饰,返回一个修改之后的方法,在打印日志,切面编程中运用广泛,你说 ruby 开放那你可以试一下在不改变函数名的情况下对原函数进行修改

    module LogDecortor
      def log_method *method_names
        mod = Module.new do
          method_names.each do |method_name|
            define_method(method_name) do |*args, &blk|
              puts "Method #{method_name}: start"
              ret = super(*args, &blk)
              puts "Method #{method_name}: end"
              ret
            end
          end
        end
        self.prepend(mod)
      end
    end
    
    class Foo
      extend LogDecortor
    
      log_method :bar
      def bar
        return 1
      end
    end
    
    p Foo.new.bar
    

    另外 Ruby 即使有不改变方法代码本身就改变方法行为的能力,但是实践中还是比较克制,像加日志、打点一类的需求有另外的方法,就不展开说了。

    第二 javascript 只是我用来举例,可能你没懂我在说什么

    只是没懂你的举的例子有什么太大的好处。

    第三纯函数式语言是写不出复杂的系统的,或者说是不容易写出复杂的系统

    纯函数式编程不容易写出复杂系统是因为程序员要求高一点。

    所以这里的函数式编程就是一个概念,允许方法有副作用

    "函数式编程就是一个概念"不是我的原话吗?我本意是现在市面上绝大部分面向对象语言都可以写“函数式编程”的代码,只是前端生态很推崇,别的领域还没有一要使用“函数式编程”的场景。

    Ruby 2.7 曾经有过管道符 https://bugs.ruby-lang.org/issues/15799 ,你对函数式编程那么了解肯定知道管道符对函数式编程的意义,虽然种种原因取消了,但是可以反映出 Ruby 是拥抱函数式编程的,你可以期待一下 ❤

  • 比如增加 python 的 AOP 特性 (装饰器)

    Ruby 足够开放,不需要语言层面装饰器。

    def 定义的方法名也是一个变量,可以覆盖(就是重复定义)类似 javascript,

    javascript 还没有好到需要别的语言模仿吧 😅 覆盖方法不就是 override 么……

    然后支持函数式编程,高阶函数(支持传入函数,传出函数),现在来看还不能算真正支持函数式。

    高阶函数核心在与“穿作用域”,而在“穿作用域”领域,Ruby 比大多数语言强很多。函数式编程也仅仅是个概念:要不你就用纯函数式编程语言,写不出违反函数式编程 pattern 的代码;要不你就用其他语言假装在函数式编程:有些领域大家都在假装,有些领域大家还没开始装,并不是说不能装,只是还没遇到需要装的场景。

    学新语言还是要有空杯的心态。

  • Rails 原生的 websocket 比较弱,这种全靠 websocket 的框架还是观望一下比较好(实在忍不住可以用 phoenix live view)

    看过Samuel Williams的 Ruby 处理百万 websocket 连接The Journey to One Million by Samuel Williams后一度对 Ruby 在稍微高一点的并发场景产生了幻想。

    Samuel Williams 主推的是基于FiberAsync做 ruby 的 websocket server,然而 Rails 团队似乎并没有打算用 Aysnc https://github.com/rails/rails/issues/35657

    关于 websocket 另一条路是用 Anycable,用 golang 或者 erlang 做 websocket server,再通过一另一个 gRPC 服务跟 Rails 应用代码交互。Stimulusrelex 文档上说已经支持 Anycable 了,但是用 Anycable 增加了部署的复杂度,会不会违背了 Stimulusrelex 的初心 😅

    With StimulusReflex, you'll ship projects faster, with smaller teams and re-discover the joy of programming.

    另外分享几个事情:

    • 好多年前就有一个基于 websocket 渲染的 Ruby web 框架 https://pakyow.com/ ,开发者也不用自己写 js 代码,跟 Rails + Stimulusrelex 体验很像,停更了几年后最近半年突然 release 1.0,用上了 Async,我猜可能是 Async 解决了他们 websocket scale 的问题。
    • Anycable 的作者做的benchmark表明,async 并没有比 puma + actioncable 好太多 😢
  • xxx.call(10)可以写成xxx.(10)或者xxx[10],都是语法糖,用多了讨人厌

  • param = []
    define_method(:method1) do
      define_method(:method2) do |x|
          param.push(x)
      end
      return method(:method2)
    end
    
    f = method1
    f.(10)
    
    p param
    
    
  • 微服务管理者能分山头,技术人员能折腾着玩,除了对产品不一定好,其他都照顾到了。

  • 一文看懂 IO 多路复用 at March 23, 2020

    一般开发者不需要操心这个,对 rails 开发者来说,这部分细节是在 puma 等 webserver 这边。puma 之前版本是用的IO.select操作 socket,绝大部分场景够用了。后来有了 actioncable 才用 nio4r(文中的 epoll),主要是突破IO.select最多只能接 1024 个 socket 对象的限制 https://github.com/puma/puma/commit/e83a4954e4c0214d18beb594ddf598fafdf058d7#diff-8b7db4d5cc4b8f6dc8feb7030baa2478

  • 没有必要把 B 放到 A 里面,你的问题是 A 以及子类依赖另外一个类,你需要把这个类传给 A 或者 A 的子类。把 B 放进 A 不解决问题。

    require 'active_support/core_ext'
    
    class B
      def b
          puts "A::B#b"
      end
    end
    
    class A
      class_attribute :b_like_class, default: B
    
      def a
        b_like_class.new.b
      end
    end
    
    class B2 < B
      def b
        puts "B2.b"
        super()
      end
    end
    
    class A_CHILD < A
      self.b_like_class = B2
    end
    
    A.new.a
    A_CHILD.new.a
    
  • class A
      class B
        def b
            puts "A::B#b"
        end
      end
    
      def a
        B.new.b
      end
    end
    
    class A_CHILD < A
      def a
        b = B.new
        def b.b
          puts "A_CHILD#B.b"
          super
        end
        b.b
      end
    end
    
  • 报错的是 https://github.com/ruby-china/homeland/blob/master/app/controllers/admin/stats_controller.rb#L11

    result[:count] = klass.unscoped.count # Line 11
    ...
    def klass                                                                      # Line 17
      params[:model].camelize.safe_constantize
    end
    

    你的 url 里是model=note,似乎代码里并没有这个 model。换一个别的试试看。

  • 如何定义“优雅”?我面试的时候经常问别人 😉

    concern 用处有两个

    1. 为 mixin 提供语法糖。
    2. 解决多个 module 相互依赖问题。

    解决依赖问题是通过 override Module#append_featuresModule#included两个钩子,希望多介绍这两个钩子。

  • concern 用途 一、用于把特定功能代码集合封装成一个 concern,提高代码可读性和方便代码重构; 二、把多个 model 或者 多个 controller 重复的功能集合代码封装成一个 concern。

    用普通的 include/module 办不到吗。

  • Rails 如何跳转外链? at February 05, 2020

    <%= link_to 'CSR', 'www.baidu.com' %><%= link_to 'CSR', 'https://www.baidu.com' %>的区别

  • 免费获得接近 SPA 的页面切换体验不香吗,不会用就别用,多大点事。

  • 全栈,前后端分离,微服务,中台是否合适都要看团队规模跟组织架构。Rails 适合小团队,提供非常实用的全栈的解决方案,turbolinks 用起来很开心;团队人多了大家编程水平跟对项目代码的整体理解参差不齐,前后端分离能解决一部分问题;分工越细致领导岗位也越多 (反着说也行)。

  • request_id 写到当前 request 对象中,差不多

    middleware ActionDispatch::RequestId do
        request.request_id = make_request_id
    
        middleware Rails::Rack::Logger do
            Rails.logger.tagged(request.reqeust_id) do
                # Your Rails Application Code
            end
        end
    end
    
  • 能力还没强到可以填补学历的差距。

  • 中了缓存还要 200ms 的话……优化空间很大😂

    我写过查看 Rails 应用调用栈的工具,可能能帮你找一下瓶颈 (发现过我们应用一个请求调 8000 多次的方法) https://github.com/piecehealth/rails_watcher

  • 可以把要统计的结果提前算出来存到另一个表里。这很像数据仓库里的 ETL 了

  • rexml 好像是纯 ruby 的

  • DistributedMutex的实现是允许超时自动释放锁的,是一个不严谨的悲观锁。超时有 warning log,可以方便追踪,或者监控报警。第一优先级还是不要让操作超时。

    真的要用悲观锁,可以参考 https://redis.io/topics/distlock

  • 首先加锁后的操作如果占用太长时间会成为瓶颈,即第一优先级是尽量快的释放锁。

    如果实在完不成,比如所有操作有 5 步,可以每步都 extend 一下锁,不用检查还有多少时间。

  • 当 A 线程还在执行代码时(但是已经超过超时时间),B 线程发现锁已经超过超时时间,然后 B 设置了新的超时时间并获取了锁(仅仅在 A 线程执行完毕后在 log 中记录一条 warning),这种做法是否合理?感觉上即使一个线程的执行时间如果超过了超时时间,其他线程也不应该获取到这个锁,而是获取锁失败。

    A 线程要负责释放锁,否则如果 A 异常退出那么就是死锁了。如果 A 在锁过期时间内执行不完,A 可以 extend 锁。

    但是在 Discourse 中的使用方式都是下面这样,所以有点迷惑,这种方式使用的话是不是就不需要 Mutex.new.synchronize 来保证原子性了:

    DistributedMutex.synchronize借助 redis 实现了跨进程/机器的锁,Mutex#synchronize实现了本进程起的线程级别的锁。的确可以只用 redis 实现线程级别的锁,但是毕竟有额外的 redis 读写操作,不如直接用内置的Mutex#synchronize

    附:Rails 中,关于多线程方面的知识可以在哪里接触到?是一个或 n 个请求就会对应一个线程,又或者是一个 session 会对应一个线程?

    Rails 用的多线程知识可能并不多,顶多是如何在多线程环境下初始化一个单例。多线程可以看 puma 或者 sidekiq。

  • 类似 py typing 的现成轮子 at December 06, 2019
  • class Form  < ActiveRecord::Base
      self.abstract_class = true
      def self.inherited klass
        klass.class_eval do
          default_scope order("#{table_name}.form_date DESC")
        end
      end
    end
    
    class SubForm < Form
    end
    
  • body= sock.read就好了,一般都用read或者read_nonblock,没见过用recv

  • ShowMeBug 核心技术内幕 at October 26, 2019

    这个场景比较适合 Elixir, 除了并发上的优势,还有 OTP 用来支持“OT 转换算法”。