11月阅读周·编写可测试的JavaScript代码:更智能的集线器:事件交换机篇
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出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畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)