11月阅读周·编写可测试的JavaScript代码:更智能的集线器:事件交换机篇

举报
叶一一 发表于 2024/11/21 18:31:30 2024/11/21
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》、《JavaScript异步编程设计快速响应的网络应用》

当前阅读周书籍编写可测试的JavaScript代码

更智能的集线器:事件交换机

事件交换机增强了基于事件的架构,使它们更加模块化和可部署。如果愿意对事件集线器和事件本身增加一点点逻辑的话,应用程序就更容易部署和管理。

通过分类将事件分成两组——广播和单播,使用事件交换机代替事件集线器,可以获得一些很巧妙的特性。除了节省网络带宽以外,最大的特性就是自动防故障安全部署。

基于事件架构的一个最大承诺就是模块化。典型的整体程序被替换成多个独立的模块,这非常有利于可测试;使用事件交换机代替集线器也非常有利于可部署性。

事件交换机知道广播事件和单播事件之间的区别。广播事件的行为就像使用事件集线器的所有事件一样。而单播则只会向对事件已经注册的监听器发送单播事件。该特性使得部署更简单,我们将在下一节中看到。

部署

通常单一应用程序的所有服务器端逻辑都与HTTP服务器交织在一起。部署一个新版本,通常要一次性部署整个应用程序逻辑,然后重启Web服务器。即便只升级应用程序的一部分,也要全部重新部署。基于事件的应用程序逻辑则完全独立于Web服务器;实际上,应用程序的逻辑是驻留在许多不同的独立模块中的。这就使得版本代码的推出更简单,模块可以完全独立于Web服务器进行更新和部署。虽然有更多的“移动部件”,但可以对部署和代码进行更细粒度的控制。

所以,如果安全地停用一个事件监听器而又不删除事件要怎么做?可以使用单播事件和广播事件。对于安全关闭监听器而又不删除事件,使用事件交换机是很有必要的。

单播事件

单播事件,就像如下代码中的depositMoney,其只有一个监听器。在这种情况下,监听器必须告知事件交换机,该事件是一个单播事件,并且它是(唯一的)监听器。当准备系统升级时,需要启动新版本的模块,该新版本模块告知事件交换机现在它是唯一的事件监听器。事件交换机将通知旧模块,其将会被取代,此时旧模块在处理完未完成的事件后,就可以卸载了。没有丢失事件,事件交换机将单播事件切换到了新的监听器上,但交换机仍会保持原有旧模块的链接,在完成所有尚未完成的事件之前,会继续处理回调或该监听器检测到的事件。通过这种方式,我们可以成功关闭旧的监听器,并启用一个新的监听器,实现无中断服务:

eventSwitch.on(
  'depositMoney',
  function (data) {
    cash + data.depositAmount;
    eventSwitch.emit('depositMoney', cash);
  },
  { type: 'unicast' },
);

当我们开始监听该事件时,这段代码通知事件交换机,这是一个单播事件,并且所有该名称的事件都只能传播给该监听器。我们所做的就是,给on方法添加第三个参数(附带一些元数据),该参数是客户端传递给交换机的。

与此同时,交换机将会把该事件(如果只有一个)的当前监听器替换为新版本的监听器,并向之前的监听器(如果只有一个的话)发送一个“嘿,有人替换你了”的事件。该事件的任何监听器都应该有一个“卸载消失”的事件监听器:

eventHub.on('eventClient:done', function (event) {
  console.log('DONE LISTENING FOR ' + event);
  // finish handling any outstanding events & then safely:
  process.exit(0);
});

eventClient:done事件表明此监听器将不再接收指定的事件,当它处理完任何未完成的事件时,就可以安全地关闭了,或者可以继续处理仍在监听的其他事件。

事件交换机的原子操作确保了没有事件被移除,且没有事件被发送到被替换的监听器上左边的模块注册了单播事件,并且事件会传递给它。右边的新版模块注册了同样的单播事件。第一个模块接收done事件,所有新的事件请求都被发送到右边的新模块上了,而左边的模块处理完当前事件后,就会关闭。新版模块继续处理事件请求,直到有另外一个新版模块通知负责单播事件的集线器。

广播事件

广播监听器的安全关闭遵循类似的过程。然而,事件监听器本身会将eventClient:done事件广播给指定事件的所有其他事件监听器,以告知它们自己要被关闭了。事件交换机足够智能,除了发出该事件的监听器以外,会将该事件广播到所有的监听器上。

监听器接收该事件后,将不再接收该指定事件的消息。处理完未完成的事件后,监听器就可以安全关闭了:

eventHub.on('eventClient:done', function (event) {
  console.log('I have been advised to stop listening for event ' + event);
  // finish handling any outstanding events & then safely:
  eventHub.removeAllListeners(event);
});

注意,事件交换机是足够智能的,其不允许任何已连接的客户端触发eventClient:done事件,如果发现就会将其移除。流氓客户端是不能夺取事件交换机的权利的!

会话

可信任的单播监听器从所抛出的事件中接收会话数据。“可信任”客户端使用共享秘钥连接到事件集线器。

现在,该客户端可以监听单播事件了。所有的单播事件都是在事件回调中接收到额外会话数据的。

eventHub:session键是一个用于标识此会话的独有字符串(UUID)。可信任的监听器在存储和访问会话数据的时候应该利用到该键。

对于不受信任的客户端,会话键值也是完全透明的。YUI3和jQuery客户端都是将会话键值持久化到cookie中。

HTTP服务器只是一个可信任的事件集线器客户端,向浏览器发生可信任的、已认证的或个性化内容时,其HTTP请求中利用了cookie中的会话键值。

可扩展性

socket.io库有很多语言版本的客户端,所以大家使用的服务不仅仅限于JavaScript客户端。对于socket.io所支持的其他语言用户,我希望在该语言下编写EventHub客户端也十分简单。

总结

使用事件交换机部署基于事件的应用程序会非常容易。它允许单独关闭旧模块,并在不丢失任何事件的情况下开启新模块。这可以让应用程序以分散的方式进行部署,而不是都部署在一起。升级的时候,不用大爆炸式的全部升级,就可以对单个模块进行单独升级,这将可以使代码和开发过程变得更加灵活。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。