• 因为真的是老生常谈了,所以我决定长篇大论一番,观点完全主观,慎看。

    微服务本身可能有两个不同角度的理解。一方面是商业,另一方面是技术

    商业上其实跟初创差不多。我认为大多数老板都只是想要快速地将想法实现而已。这种情况下他们谈微服务,只是侧重于这个东西:

    • 首先,不会导致原来的东西坏掉
    • 其次,能用最小工时和运维代价快速上线
    • 最重要的是,这个东西能随时跟其他服务组合一起卖。做多了以后还能自由组合,分别按量计费。这样子就很方便做组合销售。

    举个例子,你在给大客户推自己的老服务,结果他说这里头连起步版都很多功能不用的,你却非要我买它。而且你这里头还有一个我需要的核心功能没有实现,别家说他们可以做。但是你这边工程师说耦合很大,难以实现。

    所以总结起来,商业角度侧重于销售上的灵活性。

    但是老板叫技术负责人做微服务,这时候技术负责人未必就能理解到这个销售的需要了。

    接下来我就得谈谈技术方面对微服务的理解。

    微服务虽然他是微的,本质却是服务。所以你不得不先了解下 SOA 是什么。

    SOA 本质是给复杂的体系编程的架构。

    这个复杂可以复杂在数据的存放和集成上,例如大公司用的是很多年前的 Oracle 数据库+asp,又跟购买的各种乱七八糟的系统【例如防伪系统】等有集成。这种情况下,如何通过重新整理系统来更高效地接入新的大需求——比如引进大数据分析,新的产品线。

    这个复杂也可以体现在规模上。一方面是访问的并发甚至跨广地域上,例如社交网站等 C 端应用。除了多地高频访问带来的压力以外,还有开发测试运维客服团队规模变大带来的复杂性。入职到上手要多久?能自信地修改代码增加功能【而不会有严重回归】吗?怎么协调那么多人开发和维护产品。

    从实际选型上,你可以发现 SOA 基本都是基于事件编程,并且进出流量都是通过分布式系统来承载。由此可以看出 SOA 的较好实践离不开这俩。

    那回到一开始讨论的东西微服务,它微在哪里?大家应该都听说过重复发明轮子这句话。其实微服务只是代表在敏捷时代下新的技术栈里实现 SOA。 毕竟之前 SOA 的实现真的老了,更多的在 B 端应用,很多具体实现细节的差异让他们不能很好地适应一些现代的需求。当然另外一方面,我认为有能力开发的团队也不会买东拼西凑的 legacy 产品。

    那为什么要说成微服务呢?这个我真的没有考究。猜测一方面是跟商业有关。例如你要给一家大公司推销你利用新技术的 SOA 方案,怎么样才能凸显优越性呢?取个名字“微”服务。微小负担的先进 SOA 听着就不错。另一方面没准搞技术的人也想让新轮子能区别于过去的旧轮子。

    最后回到楼主的问题,初创搞 rails 老一套多舒服,为啥要劳民伤财地搞微服务,还要纠结这个坑爹的需求在啥都没有的微服务下要怎么绕圈做。哪怕预期的并发是 rails 无法满足的,开源社区上也有很多其他的语言和其他的框架。如果实在招不到人,那么也可以根据市场的语言偏好用跟 rails 类似的框架搞。例如 spring boot。.

    正如 matz 所言,ruby 依然很适合很多初创公司搞。毕竟一开始没有多少人会有钱搞那么大的团队,也没有多少人能对自己公司实际在线的业务规模增长有自信。但是不管怎样,反应一定要敏捷。

    所以结论,工具很多,根据实际情况出发,爱用啥就用啥。任何一门语言,只要还有人用,社区还有人贡献代码,它就能活着。不用操心。 我只需要担心自己会不会学不来,会不会找不到想要的工作。

  • 注意到 class A 里面嵌套 class B 事实上只是在打开 class A::B。继承时 B 作为不变常量只是有了个新别名。换句话说,A_CHILD::B == A::B

    关于你的需求, 如果只是A_CHILD的a方法需要特别的b方法,A类并不需要,那么写个A_CHILD#a就足够了。

    class A_CHILD < A
        def a
          puts "A_CHILD#B.b" # 你需要的特别逻辑
          super # 如果还需要原来父类A#a的逻辑
        end
    end
    
  • Ruby 抓疫情数据 at 2020年03月09日

    乱码是因为 powershell out-file 的编码默认不是 Utf8。 如果想要避免 encode,可以把输出到文件做成参数。利用 Ruby 的 File 写入文件。 另外我稍微整理了一下代码。使用标准库来下载 json,通过一个辅助方法来同时支持输出到文件或 STDOUT。

    # -*- coding: UTF-8 -*-
    require "open-uri"
    require "json"
    
    def with_outs
      case ARGV[0]
      when String
        File.open(ARGV[0], 'w') { |f| yield f }
      else
        yield STDOUT
      end
    end
    
    with_outs do |outs|
      raw_body = URI.open('https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5&callback').read
      response = JSON.parse(raw_body)
      data = JSON.parse(response['data'])
      outs.puts data['lastUpdateTime']
      data['areaTree'].each do |item|
        loc = item['name']
        count = item['total']
        line = "#{loc}累计确诊: #{count['confirm']}例, 死亡: #{count['dead']}例, 治愈: #{count['heal']}例;"
        outs.puts line
      end
    end
    
    

    在以下版本测试通过

    $PSVersionTable -> 5.1.18362.628

    ruby -v -> ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x64-mingw32]

  • 这是数据库池问题,与 puma 没有直接关系。建议你看看 postgresql 的 log。你的连接被数据库关闭了。

  • 如何实现实时的并行处理 at 2018年04月30日

    不太理解你的需求,尝试详细描述一下?

  • puma 配置的 worker、线程数其实是不能太大的。worker 数等于核心数,最大线程数等于 4 倍于核心数就已经很多了。如果并发比较均匀,最小和最大线程数可以保持一致。虽然线程数增加是有利于几乎都在等阻塞 IO 的应用,但是内存会受不了,等完阻塞 IO 的 GIL 竞争也会很严重。所以一般 worker 数 = 核心数。保守一点,例如机器还跑数据库之类的,也可以核心数减去 1、2,留点资源给他们。最小最大线程数都锁在 核心数 * 2 (或者最小是核心数,最大是 2 倍核心数) 会更加合适一些。头铁,对内存占用有自信的话,可以 4 倍。买服务器的时候都买 2 核 4GB,不够用了,就多买一台,做负载均衡(注意国内云的内网速度)。

    其实先不提 Ruby 的 GC 有没有锅,puma 是有 bug 可能导致内存溢出的。例如 3.x http1.1 persistent 连接溢出。但是代码没有严格控制风格,现在已经很乱了,找个 bug 很费劲。另外 MRI 也没有像 JProfiler 那么好的性能调优工具,找起来几乎靠片面的数据去直觉推断,挺麻烦的。如果真要用 puma,弄个 nginx 在 puma 前面挡一下,可能可以改善一点。如果应用并发比较高,还是建议上 passenger, unicorn 这些有商业背景的(并发模型也很耿直的)。虽然损失了一堆并发,但是长期运行的话,会稳一点。:)

  • 截止到现在,主要 gem 包对全局开启(--enable=frozen-string-literal)的支持依然不是特别好。

    建议还是使用魔法评论局部启动。可以使用 rubocop 帮助自动添加这个评论。以后 deprecated 后还能自动删除。:)

    对性能的提高主要跟 GC 的实现有关。如果不严谨地总结的话,对象这玩意,当然是创建得越少越好啦!尤其是字符串这种,代码逻辑里到处都在用的数据类型。不仅省内存,还省了对分配内存内核 API 的调用。万一有多线程,使用这种字符串的话,也不用考虑竞态条件了,因为这对象都不变的。

    如果学习过 C 的话,估计能更加体会到冻结字符串的好处。 :)C 的数据结构里有一个叫做原子 (Atom) 的玩意。根据字符串内容唯一对应一块内存的话。单凭一次指针(内存地址)这样的整型比较,就能完成任意长度字符串的比较了

  • 翻得不错。 CSRF token 在使用 http only cookie 的场景里起到了保护会话的作用。避免攻击者在别的域名下使用用户的会话发起带副作用的 POST 请求 如果简单总结这个模块干嘛的话:

    1. CSRF 模块会给每个 session 生成一串随机字节,为了讨论方便,称为 A。
    2. 要拿这个 token 的时候(例如调用 form_authenticity_token),再生成另外一串随机字节,称为 B。让 A 和 B 做一下做一下位异或操作,得到 C。最后把 B 和 C 拼接在一起得到 D。D 就是返回的 csrf token。由于用了 B 做了异或,这样每次请求里的 csrf token 都不一样了。
    3. 要判断浏览器发过来的 token 对不对时,假设发送过来的是 E,这 E 其实就是第二步的 D,把 E 分开得到第二步所述的 B 和 C。再做一次异或操作,A 就回来了!最后做一下比较,就得到结果啦。

    关于最后的比较 (secure_compare),额外的 TIP:为了防止 Timing Attack 这种旁道攻击,一般来说,都会对字符串做同长比较。rails 会对字符串做个摘要,这样不管字符串多长,生成出来的摘要都是一样长的。这样就可以做同长比较了。