• Hello, Faktory at 2017年11月24日

    我感觉 Mike Perham 在不断试错吧, Ruby sidekiq 成功后, 现在一直不断寻找新的突破口. 他也在不断的折腾: crystal 版本的 sidekiq, golang 实现的 inspeqtor 再到现在 golang 实现的 faktory.

    虽然现在也不太愿意使用 faktory, 但在已经稳定的 一个人的公司 仍然这样不断创新也挺佩服.

    另外, 对于不同系统之间的异构系统, 也有太多方案可选择了. 比如复杂一点就上 RabbitMQ 了.

  • @pathbox 我的理解是这个设计将下面几项单独出来:

    1. 与 RabbitMQ 之间的沟通独立出来负责与 MQ 之间的连接/重连的问题
    2. 将所有的任务看做 MQ 中的 Message, 负责 Message 的 成功/失败重试/死亡队列 的问题.(这个详细可参考 Sidekiq 的机制)

    这个设计优势会体现在使用不同的语言, 都需要使用与 MQ 之间交互, 并且大家都需要 "合理的重试机制", 保证只要消息没有成功处理, 就停留在 MQ 中. 但劣势也很明显, 每一个任务都需要具体的服务执行方提供一个结果 成功否 返回. 现在的方案中是 HTTP 的同步方案. 虽然 Golang 的 goroutine 能支撑起连接数, 但是这样的同步回调机制对服务执行方的接入会有场景限制, 那些执行时间特别长的应该不太愿意接入(想象一个 http 请求连着 5 分钟等返回). 如果考虑将这个中间件设计成为一个与外部系统异步交互的方式, 使用端应该更乐意接入.

    从另外一个角度来考虑, RabbitMQ 本身是作为多个服务之间使用消息传递解耦而存在的中间件, 如果可以将这些 "重用" 的机制以扩展或者插件的方式实现到现有语言(java, ruby, golang) 与 RabbitMQ 交互的代码中, 也不失为一种方案. 这样少一层抽象少一层理解.

    @zamia ruby 和 golang 配合很是强大 😄 提供两个 tips:

    • golang 中与 RabbitMQ 的重连问题可以考虑 cony, issue 这是个常见问题, 但 golang 的 amqp 不支持, 就类似 ruby 的 amqp 不支持, 但上层的 bunny 支持. 我们一个后端每天跑着 100w 级别任务, 服务于 MQ 连接大概 100ms, 持续稳定的运行.
    • golang 的 Package Manager 可以考虑 golang dep, glide 也官方宣布等着 dep 发布. dep 将现有代码和 gopkg.yml 计算双方的依赖进入 vendor 的方案更适合 golang 这个语言的特性一些.
  • @zamia 啊, 看到了. 可以自定义 exchange 的类型. 如果是这样, 那两者的区别就非常小了. sneakers 和 hutch 两者都很稳定. 就是 sneakers 的队列是真的有点多...

    使用 hutch 的时候, 就是每次 Hutch 启动的时候都会去申请 Queue, Exchange, Consumer. 如果有变化, 就立马报错了.

  • Sneakers 和 Hutch 底层都是使用 bunny, 翻一下代码就可以看到其做了重连处理. session.rb, reader_loop.rb

    Bunny 的底层使用的是 amq-protocol, 这一层只负责协议交互部分不负责重连的问题.

    另外应用层的 Hutch, 工具层的 Bunny, 协议层的 amq-protocol 都是 michaelklishin 核心开发者.同一个作者对这一系列的抽象很漂亮.

  • @zamia 在研究多系统交互的时候发现了 RabbitMQ 和 Sneakers, 但 Sneakers 采取的是 direct 方式的 exchange 设计, 因为他走的是高并发设计所以利用 RabbitMQ 链接的多路复用机制, 创建很多的 Channel. 为高性能采用了 direct 方式, 对 rabbitmq 的使用而言舍弃了其灵活性很高的 topic 方式的 exchange. 另外一个 Ruby 领域的 RabbitMQ 队列 Gem Hutch 则是从 topic 的 exchange 出发设计.

    • 如果是大量时间不长的小 event 事件的处理, Sneakers 比较合适.
    • 如果是多系统之间的信息沟通, Hutch 比较合适. 因为 RabbitMQ 的 topic 类型的 exchange 可以很好的广播消息, 不同系统可以根据需要将自己的 Queue 绑定一个这个 Exchange 缓存消息.

    我最后选择了使用 Hutch, 但因为 Hutch 本身不支持与 ActiveJob 的集成, 我就封装了一下 hutch-schedule, 为 Hutch 提供了:

    • 参考 Sneakers 实现了 Hutch 级别的类似 AJ 的重试递增 Error Retry 机制
    • 提供了 Hutch 与 AJ 的 Adapter 集成
    • 延续 Hutch 本身的设计, 增加了延迟任务

    异常重试

    异常重试可以分为两类:

    • 作为 ActiveJob 的后端: AJ 本身有异常处理的机制, 只要后端支持延迟推送消息处理即可.
    • gem 自己的异常处理: 这个就类似上文中的 queue 身上的 ttl 机制了. 或者类似 Sidekiq 的异常处理.

    如果使用 ActiveJob 作为后端任务队列, 那么这两层机制都是可以被应用到的. 首先是 AJ 的以后捕获处理, 如果 AJ 的超出异常了, Gem 自己的还可以再捕获一次进行重试.

    RabbitMQ 的支持

    对 RabbitMQ 也有两种消息重试机制:

    • 一种是在 Queue 身上申明 x-message-ttl 以及 x-dead-letter-exchange 的死信机制.
    • 一种是在 Message 身上的参数 expiration (per-message) 的 TTL 机制.

    单一的死信机制无法达到阶梯式重试, 但和 TTL 结合起来就可以了.

    `ttl message`  -> 'topic exchange' -> 'dead queue' ...wait... -> 'topic exchange' -> 'queue'
                                                       | ttl 触发死信机制, 重新投递 |
    

    其他经验

    现在的 Sidekiq, Sneakers, Hutch 三种都采用了最基本的线程池的方式处理, 他们相似的原理表示不会有数量级上的差距. 唯独 Sneakers 在线程池的实现上还有一层类似 puma 的多进程 + 多线程(supervisor), 这个会有优势.

    对于 RabbitMQ 与 Redis 的区别, 如果纯粹是 background job, 其实 sidekiq + redis 这种 pull 方式更合适, 因为简单直接也高效. 但对于需要外部系统交互, 特别是与其他语言的异步解耦交互, RabbitMQ 所提供的消息中间件是很优势. (我现在是 Ruby 前端与 Golang 重后端之间通过 RabbitMQ 交互数据), 另外 RabbitMQ 是主动推送消息(可通过 prefetch 控制量), 在节点变多之后还承担了一个消息负载均衡投递的职责, 这个很省心.

  • #3楼 @hooooopo 那不就是 npm 吗 - -||

  • 什么都不说, 直接 "赞" 👍

  • bundler 针对 production 环境的处理, 也就是现在 npm 那样, 当前项目的依赖, 就在项目自己的目录里面不要去寻找系统级的.

    详细可阅读: http://bundler.io/v1.10/deploying.html#deploying-your-application

    同时, 调用时使用 bundle exec rails

  • @exherb 是的是的, 大家都忙去了~
    我来凑热闹达 ☺

EasyAcc® 技术合伙人. 超爱折腾乱七八糟东西的菜鸟产品经理,管理新兵