写点什么

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:504231
用户头像

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

关注

评论

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

小令动态丨令牌云获中国金融科技·最佳新锐企业奖

令牌云数字身份

创新大赛 金融科技创新

流程的价值一,固化业务的最佳实践!

CTO技术共享

深入理解跨域和最佳实践分享

Crazy Urus

面试 前端 HTTP 跨域

人人都在聊的云原生数据库Serverless到底是什么?

华为云开发者联盟

数据库 Serverless 云原生 华为云 GaussDB

什么是NFT链游项目游戏系统开发技术(Demo)采用Solidity 智能合约系统开发方案

I8O28578624

启科 QuTrunk+Runtime+QuSaaS+亚马逊云科技量子计算编程实战

亚马逊云科技 (Amazon Web Services)

Python 量子计算 Amazon EC2 Hero 专栏 Amazon Braket

MySQL:如何给字符串加一个高效索引?

程序员拾山

MySQL

树与二叉树深度剖析(二)

C++后台开发

数据结构 算法 二叉树 红黑树 Linux服务器开发

佛萨奇2.0系统开发解析逻辑教程方案(成熟技术)

I8O28578624

流程的作用是服务于业务,所有不能被用来帮业务部门好好打粮食的流程,都不是好流程!

CTO技术共享

比Postman更懂中国程序员,Apipost真香!

不想敲代码

接口测试 API 研发管理工具

DAPP/去中心化系统开发流程解析方案(成熟理念)分析结果

I8O28578624

嘉为蓝鲸IT服务管理解决方案入选2022广东省政务服务创新解决方案

嘉为蓝鲸

自动化运维 嘉为蓝鲸 IT服务管理中心

栉风沐雨 韧性前行 | 2022年九科大事件

九科Ninetech

RPA 超自动化 流程挖掘

CleanMyMac4.12.3中文版如何汉化免费?

茶色酒

CleanMyMac4.12.3

NFT元宇宙链游游戏项目系统开发技术解析(Demo)

I8O28578624

设计模式之装饰者模式

程序员大彬

Java 设计模式

“零信任”下的防火墙策略管理

智维数据

大数据 防火墙 数据可视化 智能运维 运维安全

如何让Java编译器帮你写代码

京东科技开发者

后端 编译器 java; 编译器原理 企业号 1 月 PK 榜

30+亮眼指标,看看2022年嘉为蓝鲸的逆势创新之路!

嘉为蓝鲸

自动化运维 嘉为蓝鲸 2022大事件

阿里巴巴最新版“Java性能优化实践文档来袭”把性能优化玩的出神入化

架构师之道

Java 编程 性能优化

Databend 内幕大揭秘第二弹 - Data Source

Databend

浅谈区块链项目开发技术(Solidity成熟语言)

I8O28578624

Studio One6永久免费版本下载安装包

茶色酒

Studio One6

在Spring异步线程池中自动传递上下文,这样写轻松又方便

程序员拾山

Spring Boot #java

Lattice - 模式级复用的能力定义

原力在线

架构 lattice 高可扩展

软件测试/测试开发 | 接口自动化测试中,文件上传该如何测试?

测试人

软件测试 自动化测试 接口测试 测试开发 文件上传

谈谈enabled_shared_from_this

SkyFire

c++ 智能指针

架构训练营第10期模块5作业

Geek_4db2d5

Golang如何优雅接入多个远程配置中心?

王中阳Go

golang 高效工作 学习方法 后端 viper

智能合约DAPP项目系统开发技术逻辑(dEOM)

I8O28578624

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