为了账号安全,请及时绑定邮箱和手机立即绑定

软件里根本没有真正的“实时”这回事

在2018年的GOTO芝加哥大会上,我有幸听到了Erlang的创始人之一Joe Armstrong的演讲,他的一些话给我留下了深刻的印象。在这次演讲中,他谈到了《错误处理的注意事项》(The Do’s and Don’ts of Error Handling),并且他表示,Erlang的一个特性是遵循物理学定律。

这个幻灯片让我有点犹豫,我想,“他这句话是什么意思呢?”他解释说,他不同意Tony Hoare和其他一些计算机科学学者的看法,他们认为你可以进行真正的同步消息传递,因为“你怎么能做到真正的同步消息传递呢?光速不是无限的”(你可以看看他在前10分钟左右的演讲,了解更多解释)。

在那次会议上,他还曾在问答时间中与人有过激烈的交锋。一位观众似乎对乔在这方面的智慧提出质疑,但乔只是反复强调信息无法以超过光速的速度传播,然后转向了下一个问题。(我没能找到那段演讲的录音,但希望它存在于某个地方。)

我相信他想说的是,即使是对一个对象进行简单的方法调用,设置RAM和CPU中位的电流速度也会受到光速的限制,所以从技术上来说,它并不是瞬间完成的。我不得不承认,到这个时候,我开始想,也许乔就是一个有点古怪的老派学术派程序员,这里有点过于挑剔了。不过,我带着深刻的印象离开了那个会议,即乔认为在编程中,没有什么是真正的同步;一切都是受到光速的制约。

现在我不确定自己是否能在实践中完全同意他的说法,但多年来,我确实越来越欣赏他话中的精神,并且我认为,在开发软件时我们可以在做技术决策时采用这种哲学。

实际案例

在我职业生涯的早期,我为一家餐厅开发了一个网站应用,用来接受在线订单(这在如今像Clover、Square这样的现成餐厅软件出现之前)。他们并不需要太复杂的功能,只需要一些供顾客提交订单的表单,以及一个管理员界面,用来显示他们收到的订单列表。餐厅里安装了一台电脑显示器,用来显示网络应用中的订单列表,厨师会通过这台显示器来查看需要做的菜。

网页应用的关键要求之一是订单仪表板需要实时更新。餐厅厨房非常忙碌,厨师的手很脏,没有人愿意记得去点击刷新按钮以获取新订单,因此,老板希望页面能够自动更新,以便新订单一来就能看到。这很合理。

我和另一位开发人员在这个项目上合作,我们首先想到的是使用WebSocket技术。网上有很多关于使用WebSocket实现实时功能的博客文章和教程。维基百科上关于WebSocket的页面描述了这一协议可以实现“从服务器到客户端的实时数据传输”。直到现在,如果你向ChatGPT询问如何在网页应用中实现实时更新,它首先推荐使用WebSocket。作为开发者,当我们听说需要实时更新功能时,自然而然地选择了大家都推荐的实时技术,也就没再多想其他。

不过,我不太记得这件事是怎么发生的,但在讨论到很晚的时候,我们还有很多功能要开发,而且时间很紧,于是有人就想到了,觉得不用再设置 Socket.io,为什么不直接这样做呢?

    setInterval(async () => {  
      const response = await fetch('/orders');  
      const data = await response.json();  
      setOrders(data);  
    }, 5000);

每隔5000毫秒从'/orders'接口获取订单信息并更新状态。下面的代码每隔5000毫秒自动从服务器获取订单数据,并更新页面显示。

写那段代码大概用了10秒钟。我们测试了一下,结果成功了!因为我们已经有了API路由,所以不需要额外的工作,技术上已经满足了要求。订单页面会每5秒自动更新一次。

但不知为什么,写那段代码时感觉很别扭。不会那么简单吧?肯定没这么简单。“setTimeout不是实时的。这样肯定无法扩展。”我们还找了各种理由来自我安慰。但最后我们还是发布了,结果呢,发现我们从未收到过用户抱怨订单屏幕没有“实时”更新的反馈。

学到的教训

说实话,我也说不清为什么看到那段代码会感觉那么糟糕,但我遇到过很多开发者也有同样的感觉。我认为很多开发者经常会错误地认为一切事情都应该尽可能快地完成,外部系统应该立即更新,所有东西都时刻保持同步。我们听到关于那些依赖定时任务或夜间批处理作业的旧系统的故事时会嘲笑它们。“我们比他们强多了,我们用的是现代技术栈!那些需要24到48小时才能同步的系统已经成为过去。”我们这样告诉自己。所以我们努力写出最快最好的代码,让所有东西都实现“实时”更新。我们绝不会像使用这种方式的setTimeout这样的方法来定期更新数据。

然而,如果我们采用乔的智慧,调整一下我们的思维方式,我们会觉得那段代码好很多。如果我们接受我们所建立的一切都无法真正达到“实时”(因为所有事物都受到光速的限制),问题就变成了“什么样的延迟是可以接受的呢?”比如我们餐厅订单页面的情况,可接受的延迟至少是5秒,我认为在很多情况下,可接受的延迟往往比我们想象的要长得多。

再来一个例子

最近我在做一个web应用,需要实现应用内的通知功能。要求是在页面右上角创建一个带有小红点的经典铃铛图标,用来通知用户有新的动向,如果用户被提及在评论中、被分配了任务等。但最重要的是产品经理希望这些通知能在用户不刷新页面的情况下自动更新显示。

我们之前都经历过类似的情况

另一位团队成员兴奋地说,他们非常兴奋,因为很快就要把Firebase集成到应用里了。他们真的很想开发后端代码,以更新Firebase数据存储,并打算使用Firebase客户端库来实现实时更新。每当用户被提及在评论中时,图标上的小红点会在瞬间出现。

好的。但为什么?我们之前没有用过Firebase,所以这又多了一个依赖需要引入和管理,还要写更多的代码来保持Firebase与数据库同步,本地开发现在需要通过gcloud CLI来认证Firebase等等。为了实现这个功能,这实际上增加了不少额外的工作和复杂性,而我们其实只要在useEffect中加个setTimeout就搞定了。

然而,尽管很明显这会减少工作负担,这对用户来说仍然会是一个好的体验吗?如果我们记得没有事情是瞬间完成的,这意味着 WebSocket 或 Firebase 或其他实时技术仍然有大约 100 毫秒的延迟,从服务器通过网络发送数据到浏览器。如果你离数据中心很近,带宽又充足,你甚至可能在 50 毫秒内收到更新。所以我们应该问自己,“如果通知在 100 毫秒内显示,与在 1 秒内显示相比,用户体验会变差吗?5 秒?10 秒?”

我个人认为,即使延迟最多30秒,也没有哪个用户会注意到这一点。真的会有用户坐在电脑前盯着通知图标等消息吗?还是他们忙于工作和生活,只是偶尔查看一下网站应用?那么,为什么要额外费劲去实现所谓的“实时”功能呢,如果这并不会给用户带来不便?

实践一下

我认为很多产品经理、业务人员和非技术人员对“实时”这个词有不同的理解,他们没有意识到这些不同的理解会导致技术实现上有很大的区别。有时候开发人员不愿意或觉得自己无法反驳需求,所以他们会照字面意思开始实现他们所理解的“实时”功能,尽管他们心中的“实时”概念可能完全不同。

所以开发者们,注意诸如“实时”、“立即更新”、“无需刷新”或“即时”的词语。如果你被要求构建类似的东西,首先询问业务人员:“多久算太久了?”以餐厅为例,如果以分钟来计算等待厨师开始制作订单的时间可能就太久了,但如果是几秒钟,可能还行。通知?几分钟内收到通知也还可以接受。账单?很多账单系统可能需要24小时更新也没什么问题。

是的,总是会有例外。也许戈登·雷姆西的厨房在50毫秒内完成订单,但关键是要弄清楚速度需求,在构建之前确定具体需要多快的速度,然后使用最简单的有效解决方案。记住,没有什么是实时的。关键在于它需要多快。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
手记
粉丝
9
获赞与收藏
27

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消