【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

Stomperl:基于 Erlang 的消息中间件

  • 2007-12-20
  • 本文字数:3542 字

    阅读完需:约 12 分钟

(读者可以在 http://stomperl.googlecode.com/ 看到本文介绍的名为“Stomperl”的开源项目。)

我对 Erlang 的兴趣是从去年就已经开始了。但是在断断续续的学习过程中,一个问题一直困扰着我:Erlang 究竟能干什么。就像我在 6 月的一篇 blog 里所写的:

什么时候才需要这样“rocks”的进程管理能力?什么时候才需要如此清晰的进程建模?当然高性能服务器需要。但高性能服务器不是谁都会拿着玩玩的。Ruby on Rails 会走红,因为谁都可以拿它做个网站然后做创业梦。那么 Erlang 呢?它带来的联想是什么?

有必要向对 Erlang 感到陌生的读者做一点背景介绍。作为爱立信在多年前发明的编程语言,Erlang 一直主要被应用在电信领域,并且因为它强大的并行程序设计能力而为人称道。但在很长的时间里,Erlang 一直被视为电信行业的专用工具(它的基础库名叫 OTP,那是 Open Telecom Platform 的缩写,由此可见 Erlang 与电信领域的渊源)。直到前两年,像我这样从事企业应用开发的程序员还是只能通过一篇篇零散而枯燥的论文来了解 Erlang。但随着多核时代的到来,并行计算突然就成了热门话题,Erlang 于是逐渐步入了大众的视野。先是一些函数式编程的爱好者越来越频繁地在各种场合介绍Erlang,然后以“实用主义”著称的Pragmatic Programmers 出版了《 Programming Erlang 》一书。今年这阵风潮也影响到了国内, Erlang 中文社区的成立和两届CN Erlounge 的召开让我们越来越清晰地感觉到这种被昵称为“二郎”的编程语言发出的热度。我之所以投入时间来学习Erlang,主要就是想知道企业软件开发的社区为何对它产生了如此浓厚的兴趣。

尽管买了那本《 Programming Erlang 》,但因为找不到一个适当的项目来练习,学 Erlang 的进展还是不大。在随后的几个月里,我一直努力保持着对 Erlang 社区的关注度。我尝试了 ErlyWeb,也想过在 Ruby 或者 Python 之类的脚本语言里借鉴Erlang 的好处,还在 Erlang 文档计划翻译了两篇论文。直到最近,偶然在一个讨论中听说 Twitter 为提高性能而用到了 Erlang 实现的消息中间件,于是就萌发出动手做一个消息中间件的念头。把几种主要的协议看过一遍之后,我把目标锁定在 Stomp 上,原因就是它足够简单。正如 Stomp 网站上介绍的:

Stomp 是一个非常简单而容易实现的协议……服务器端要做得好可能还不太容易,但要做一个客户端是非常轻松的,你甚至可以用 Telnet 登录到 Stomp broker 并与其交互!

目标确定了,然后就要一步步向前走。我搜索到了 erlstomp 这个项目,但其中的参与者们讨论了好几个月却还没开始写一行代码。不过 Kurt 的一个计划给我指出了方向。和别的通信协议一样,Stomp 无非是规定了客户端和服务器之间对话的语义。(下图描绘了一个经过简化的、典型的 Stomp 通信序列。)先让双方能对话,然后逐渐完善语义,这就是我要做的两件事。

又经过半天的搜索,我发现了一篇题为“采用OTP 原则构造非阻塞的TCP 服务器”的文章和另一篇日语文章,它们都描述了如何搭建一个基本的TCP 服务器架构,并实现了一个最简单的通信协议:echo(客户端发送什么,服务器端就原样发回什么)。抄袭了这个基础,剩下的事情就相对清晰了,无非是一点点把我想要的协议实现出来。同时我还加上了 EUnit 单元测试,用 Gozirra (一个 Java 实现的 Stomp 客户端)创建了验收测试。一个星期业余时间的忙碌之后,这个 broker 已经可以跟验收测试的客户端通畅对话了。于是我给它起了个名字叫“ Stomperl ”(看得出来这是个丝毫不费脑子的名字),发布了0.0.1 版本。可以看到,此时Stomperl 的架构基本上还是第一天抄袭来的那个样子:

+----------------+<br></br> | tcp_server_sup |<br></br> +--------+-------+<br></br> | (one_for_one)<br></br> +----------------+---------+<br></br> | |<br></br> +-------+------+ +-------+--------+<br></br> | tcp_acceptor | + tcp_client_sup |<br></br> +--------------+ +-------+--------+<br></br> | (simple_one_for_one)<br></br> +-----|---------+<br></br> +-------|--------+|<br></br> +--------+-------+|+<br></br> | tcp_stomp |+<br></br> +--------+-------+<br></br> | (one for each)<br></br> +-------+--------+<br></br> | mailer |<br></br> +-------+--------+实现 Stomp 协议的过程轻松得出人意料。一旦习惯了用 Erlang 的方式来看待手中的多个进程,一些在 Java 或者 Ruby 里会相当棘手的并发编程问题就成了小菜一碟,我能够像操作对象一样轻松地操作多个进程,而且还能够清晰地描述它们(前面的架构图中所有的矩形框都是进程)。而且 OTP 的 supervisor 机制实在是一大惊喜:负责处理的tcp_stomp进程如果发生任何异常而崩溃,只会导致与其对应的一个客户端通信失败,作为 supervisor 的tcp_client_sup进程会帮它处理好一切后事,不会让影响波及整个服务器。让我最费脑筋的反而是另一个看似简单的问题:信息放在什么地方最合理。在一篇 blog 里我把保存信息的方式分为参数传递、进程字典、ETS、DETS 和 Mnesia 五种,并在最后总结道:

应该首先考虑靠前的手段,如果有明确的理由表明一种手段不能满足需要时才可以考虑比较靠后的手段。这很费脑子,有时让人沮丧。但经过深思熟虑的程序好过不假思索的程序,发现自己犯错好过犯错而不自知。

在 Stomperl 里,每个 tcp_stomp 进程(以及与之对应的 mailer 进程)负责处理一个客户端的 socket 连接。在这些进程之间有两项信息需要共享:

  • 订阅信息,所有进程都需要知道哪个客户端订阅了哪个消息主题(或者队列);
  • 消息本身需要从接收端传递到发送端。

一开始我曾经考虑用 DETS 来保存订阅信息,但经过认真考虑之后发现,在这种情况下使用 DETS 会造成较大的开销(每次消息传递都需要读写文件),而目前 Stomperl 并不支持多节点分布式的部署,因此 DETS 不带来额外的好处。于是我转而使用了 ETS:在整个系统的“顶端”(即 tcp_server_sup 进程启动时)创建一个 ETS 表,逐次传递给所有子进程,在这张表里存放所有需要在进程间共享的信息。做这个重构花了一些时间,因为几乎所有的函数都需要加上一个参数来传递 ETS 表。但重构完成以后,“消息队列如何实现”的问题也迎刃而解了:直接用这个 ETS 表来实现消息队列,所有进程都能到其中查找自己需要的消息。

刚开始做 Stomperl 的时候我对消息中间件几乎一无所知,“消息发送失败时该做什么”这个问题曾经让我困扰了两三天:如果一条消息没有人接收,它应该被保存起来吗?如果一个订阅者接收成功而另一个接收失败,还应该重发吗?直到与 Stomp 协议的维护者们进行一次讨论之后,才澄清了我的疑惑:

你究竟是采用“消息队列”模式还是“发布 / 订阅”模式?或者说,消息的目标是队列(queue)还是主题(topic)?如果是队列,那么 broker 只把消息发给第一个合适的订阅者,一旦成功就不会尝试发给第二个订阅者。队列里的消息只发送一次、给一个订阅者。如果是主题,那么 broker 会把消息发布给两个订阅者,如果其中之一失败了,它也不会尝试重发,因为按照定义主题就只是把消息发布给所有订阅者,不管这些订阅者是否真的使用(甚至收到)这些消息。

我在 0.0.1 版本中的实现方式实际上是一个“发布 / 订阅”模式。经过一番研究以后我发现大多数消息中间件至少支持消息队列,其中一些同时也支持“发布 / 订阅”模式,于是我又花了一些时间来实现消息队列。还好,得益于 Erlang 强大的进程建模能力,这一步的难度并不大。

除了可以用来练习 Erlang 编程之外,Stomperl 还有什么真实的用途呢?实际上这个问题主要归结为“Stomp 有什么真实的用途”。Stomp 的主要优势还是简单。由于简单,互联网应用、甚至是某些特殊的桌面应用都可以考虑在其中引入这样一个消息总线,从而给应用的各个部分解耦。譬如说 Selenium 或者 Eft 之类的功能测试工具都可能把 TestRunner 和 TestEngine 两部分拆开,后者把用户的输入(可能是某种 DSL)发送给前者去执行,这时就可以考虑引入一个 Stomp broker 来给两部分牵线搭桥。而且借助 StompConnect 可以把任何 JMS broker 包装成 Stomp broker,这也就使得编写 Stomp 客户端程序的工作不大可能被浪费——因为几乎所有重要的消息中间件都支持 JMS。

那么 Stomperl 呢?正如我在项目主页上写的,既然用了 Erlang,那么高效率、可伸缩性、可靠性和并行程序设计的优雅都是题中应有之义。截至今天,我已经准备好发布 0.0.2 版本,这个版本同时支持消息队列和“发布 / 订阅”两种消息模式,并且完整支持了 Stomp 协议中描述的所有消息格式,而代码规模还不到 1000 行。随后我还会为它做更多的兼容性测试和性能测试,并努力为它找到一个实际应用的场景。如果你恰好有一个项目需要一个简单的消息中间件,不妨考虑一下 Stomperl,或许它也会给你一个惊喜。

2007-12-20 20:504087
用户头像

发布了 21 篇内容, 共 29102 次阅读, 收获喜欢 2 次。

关注

评论

发布
暂无评论
发现更多内容

Kafka实战宝典:一文带解决Kafka常见故障处理

数据社

kafka 监控

你真的懂怎么写`服务层`吗?

三钻

php 程序员 后端 服务 架构思维

DSN主流项目调研1——Storj和Arweave的简介

AIbot

分布式存储 区块链+ 分布式文件存储 Storj Arweave

写给想学和在学编程的你们,学习编程的7个好处

三钻

学习 程序员 软件开发 编程之路 经验分享

告别下载速度慢!Docker配置阿里云镜像仓库

程序员的时光

Docker 阿里云

国内程序员最容易发音错误的单词集合

程序员生活志

程序员 经验总结

国家央行数字货币的优势与挑战

CECBC

数字货币 央行 商业银行

6个高效学习编程的方法

三钻

学习 程序人生 大前端 后端

带你体验Vue2和Vue3开发组件有什么区别

三钻

Java Vue 大前端 Vue3 React

Python的Twisted事件驱动的网络引擎框架

陈磊@Criss

Git使用教程:最详细、最傻瓜、最浅显、真正手把手教!

程序员生活志

git

DSN 主流项目调研 0——IPFS&&Filecoin白皮书总结

AIbot

区块链 分布式存储 分布式文件存储 IPFS Filecoin

DockerFile 详解

陈磊@Criss

Java的Override和Overload

陈磊@Criss

优质单元测试的十大标准,你有遵循吗?

禅道项目管理

项目管理 单元测试 自动化测试

Kafka实战宝典:如何跨机房传输数据

数据社

大数据 kafka 跨机房

分布式定时任务调度框架实践

vivo互联网技术

大数据 分布式 框架

用“易于改编”原则,提升编程水平,写出更好的代码

三钻

程序员 大前端 后端 经验分享 编程风格

IDEA直连服务器,进行项目Docker部署,实现一键部署、启动

xcbeyond

Java Docker idea插件

一文道尽“表驱动法”

架构精进之路

编码 表驱动法

如何选择一个性能测试工具(LoadRunner和Locust的一次对比)

陈磊@Criss

华章25周年活动——《迁移学习》限量5折!

华章IT

5大法则助你 成为更出色的开发者

三钻

学习 编程 程序员 敏捷开发 经验分享

快速掌握的测试用例优先级划分方法

陈磊@Criss

人人都可以掌握的正交试验设计测试用例方法

陈磊@Criss

聊聊微前端的原理和实践

vivo互联网技术

大前端

好玩又好用,一款轻松就可以实现音视频的Demo

anyRTC开发者

音视频 移动互联网 RTC anyRTC Demo

更优雅的编写JavaScript

三钻

Java 程序员 大前端 经验分享 ES6

你还应该知道的哈希冲突解决策略

vivo互联网技术

哈希冲突

区块链技术在银行业的运用

CECBC

区块链 信息安全 金融

CentOS7 下Docker安装、启动

xcbeyond

Docker 软件安装

Stomperl:基于Erlang的消息中间件_Erlang_Jeff Xiong_InfoQ精选文章