惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

BMPI

一个 WebRTC 聊天室的三次演进 我是如何构建一个 AI 原生量化系统的 构建自己的信息简报 2.0 我的退休计划:把无期变成有期 从情绪化交易到系统化投资 反脆弱 系统化思维 策引2025 实盘大考:当算法跑赢人性 三十五 我的AI投资助手 我的投资之路 BMPI周记008:设止损而勇试错 BMPI周记007:经济下行 BMPI周记006:时光机 BMPI周记005:直播小完结 BMPI周记004:不要做空 BMPI周记003:十年前的今天 BMPI周记002:交易之难 BMPI周记001:开始直播 对交易的思考 AI驱动开发:从Prompt到Product(直播) 策引全球投资组合:A股1号 策引全球投资组合:A股2号 策引全球投资组合:A股3号 策引全球投资组合:A股全球 策引全球投资组合:加密币1号 策引全球投资组合:加密币2号 策引全球投资组合:美股1号 策引全球投资组合:美股2号 策引全球投资组合:美股3号 策引全球投资组合:美股4号 策引全球投资组合:美股全球 全球投资开户完全指南:A股与美股篇 我的2023 全球经济中的隐形巨人 海外银行证券开户薅羊毛小记 我与ChatGPT结对编程的体验 ChatGPT背后的语言模型简史 ChatGPT应用开发小记 我的AI阅读助手 SQLite的文艺复兴 2022亏了多少 # 组合季报(2022Q4) 我的2022 AI降临 构建自己的信息简报 善用GitHub 我的巨亏经历 # 组合季报(2022Q3) Google软件工程之工具篇 构建自己的杠杆 我的投资助手 Google软件工程之过程篇 Google软件工程之文化篇 交易之难 # 组合季报(2022Q2) 从技术难题中学习 编程语言是如何实现并发的之并发模型篇 疫情与战争 # 组合季报(2022Q1) 编程语言是如何实现并发的之操作系统篇 编程语言是如何实现泛型的 写在第二十五万字 走进 Web3 财富常识 复盘2021 # 组合季报(2021Q4) 我的2021 分布式系统中的时间 分布式系统下的认证与授权 如何学习一门技术 K8S 云原生应用开发小记 使用 Beancount 管理家庭财务 三周年小记 # 组合月报(202109) 仓位管理是核心 # 组合月报(202108) 云端 IDE 护城河还在但城没了 # 组合月报(202107) 构建高质量的信息输入渠道 最牛指数 # 组合月报(202106) 国际化与本地化 慢慢变富 # 组合月报(202105) 失败驱动开发 加密币挖矿小记 家庭资产配置的阶段 # 组合月报(202104) OKR + GTD + Note => Logseq 我的个人项目技术栈 长期投资之难 # 组合月报(202103) 我的人生管理系统 重新思考估值策略 # 组合月报(202102) 当别人的股票基金上涨时 # 组合月报(202101) 基于Serverless实现静态博客访问统计功能 构建终身学习体系进行自我提升 我的家庭理财规划 投资的秘密 # 组合月报(202012) 零成本搭建现代博客之优化国内访问速度 我的2020 我的绘图工具箱 关于银行分期贷款的坑 # 组合月报(202011) 投资交易的心理建设 # 组合月报(202010) 大跌时我们能做什么 # 组合月报(202009) 双均线交易策略 # 组合月报(202008) Serverless应用开发小记 投资理财书籍推荐 # 组合月报(202007) 我的笔记系统 200万小目标 # 组合月报(202006)
Real-time Web应用开发新体验
2022-10-31 · via BMPI

Real-time Web是什么

Web正变的实时可交互(Real-time Interactive)起来,从早期的内容发布通知类应用,再到移动时代社交媒体的分享类应用,现在逐渐涌现出很多实时协作类的应用。

The evolving online user experience.

The evolving online user experience.

这种变迁的驱动是由底层技术发展而带来了的,进而提升了Web类应用的用户交互体验。比如Web应用前后端通信协议从单向的HTTP REST到双向的WebSockets的发展,带来了Web用户体验更快的更新速度与更强的交互能力。

按照更新与交互的差异,Real-time Web又可以分为如下两个类型。

实时更新类:

  • 信息流(Feed):资讯类网站实时更新突发新闻,社交媒体Feed流;
  • 通知消息:社区通知,如知乎的问题回答更新通知功能;
  • 金融证券:实时更新股票价格数据;

实时交互类:

  • 直播活动:观众可对直播内容实时发送评论、弹幕等;
  • 位置共享应用:多个用户可在同一房间实时共享自己的地理位置;
  • IM群聊应用:多个用户可在同一房间使用文本、语音或视频来聊天,如WeChat或Whatsapp,也有开源的实现如Tinode
  • 虚拟空间:多个用户可在虚拟空间里办公或游戏,如在gather里可以创建一个虚拟团队,成员能在虚拟空间里远程办公(如视频会议与白板协作),甚至可以拥有个人的虚拟办公室;
  • 实时协作:多个用户可在同一房间实时协作,如在线白板类应用excalidraw或原型设计工具figma
  • 在线游戏:多个用户可在同一房间玩实时对战游戏;

Real-time带来的技术难题

Real-time相比之前的HTTP推拉机制(GET/POST),对服务端来说,挑战从如何处理大量的短连接,变成了管理大量的长连接。

在各类编程语言多种并发模型的支持下,再加上一些内核调优(kernel tuning)配置,现在的后端服务可以用HTTP协议在单机里并发处理上百万的短连接请求,更多详见这篇文章:

单机长连接管理

虽然在短连接上,单机并发上百万不是非常复杂的事情,但在长连接的管理上,单机上百万还是存在一定的难度,尤其是不同编程语言在实现方面还存在较大的差异:

Real-time相比传统Web来说,除了连接从短变成了长,每个用户的交互时间也变得更长。如果一个用户至少用一个长连接,那单机的架构很快就成为了瓶颈,唯一的办法是通过集群扩容解决单机资源瓶颈的问题。

但集群分布式架构的引入可能会带来更复杂的架构问题,如集群的扩容、数据状态的管理等。

分布式集群扩容

在分布式架构中,无状态的服务,可以很容易通过水平扩容(Horizontal Scaling)来提高服务吞吐量(Throughput)。如果是有状态的话,可先采用垂直扩容(Vertical Scaling),比如提升单机的硬件性能的方式,如果还是无法达到系统要求,则通过数据分区(Partitioning)的方式将数据分散存储在多个节点中。

数据状态的难题

由于数据分区很容易产生更复杂的分布式难题,比如节点添加删除导致的数据分区再平衡问题、数据一致性问题、分布式事务与共识问题等。所以尽可能把运行时的服务与数据拆分开,把服务做成无状态的,数据通过流处理系统如一些中间件去处理。但引入中间件又会带来额外的维护开销,也会让架构变的更为复杂。

而且Real-time类型的应用架构一般是基于异步事件驱动,而非实时数据同步的方式去实现的。异步事件驱动的架构在可观测性(Observability)上更困难一些,比如要在系统运行时去查看某事件流引发的系统行为,很难通过有限的日志去观测系统所有组件的运行时状态。

IM集群的例子

以IM聊天应用Tinode为例,它的集群是这么设计的:

假设集群有S1、S2与S3三个节点,有A、B与C三个用户,A加入了聊天室(T1, T3),B加入了(T2, T4),C加入了(T3, T6)。S1节点处理聊天室(T1, T2),S2处理(T3, T4),S3处理(T5, T6)。用户的客户端可以接入集群任意一个节点的API Endpoint。此时的数据处理链路如下:

A(T1, T3) -> S1(T1, T2) .(forward). T3 -> S2
B(T2, T4) -> S2(T3, T4) .(forward). T2 -> S1
C(T3, T6) -> S3(T5, T6) .(forward). T3 -> S2

这里的.(forward).指节点无法处理此请求,将请求转发到合适(通过一个全局的路由映射表查找)的节点去处理。

假设此刻S2下线,S2处理的T3与T4需要分区再平衡到S1与S3节点上,在这里可以用一致性哈希算法(Consistent hashing)处理集群节点数据(这里指的是聊天室状态)再平衡的问题。节点一致性可以通过诸如Raft的共识算法达成共识。此时的数据处理链路如下:

A(T1, T3) -> S1(T1, T2, T4) .(forward). T3 -> S3
B(T2, T4) -x-> S2()
C(T3, T6) -> S3(T5, T6, T3)

当S2下线后,B的客户端检测连接断开后重新接入其他节点比如S1或S3,也可以在服务端部署HAProxy或Nginx去自动处理。由于A和C的T3聊天室再平衡到S3节点了,A和C的客户端需要重新加入到S3节点的T3聊天室。当S2节点重新上线时,需要再平衡至之前的状态,相应的客户端也需要重新接入。

这里的聊天室在实现时一般是在WebSocket上订阅的Channel名。

Elixir的解决之道

Real-time叠加分布式带来的难题让可交互的实时Web应用开发变的复杂起来。但基于Erlang/OTPElixir语言让这类应用的开发变的简单起来。

Elixir继承了Erlang/OTP的很多特性:天然分布式、容错、低延迟等。关于Elixir的进一步介绍可以参考我之前写的一个Slide:Elixir介绍

Elixir是如何解决Real-time Web应用开发的痛点?下面从两个方面来介绍。

让一切Live起来

得益于Erlang/OTP出色的用户线程模型,Elixir的Web框架Phoenix可以轻松在单机处理上百万的长连接:

为了发挥如此出色的长连接管理能力,Phoenix框架更是将Server-driven UI发挥到了极致:

JS driven UI vs LiveView driven UI

JS driven UI vs LiveView driven UI

如图所示,左侧是常规的Web页面渲染机制,数据通过网络从服务端拉取,前端通过数据控制页面组件的渲染。而Phoenix的LiveView可以让后端开发者摆脱写Javascript的困扰,所有的逻辑都在服务端完成,页面的渲染也是通过服务端计算页面更新的差异,将小部分差异通过WebSocket推送给浏览器。

Elixir让一切Live起来!

让分布式变得简单起来

除了优异的长连接管理能力,得益于Erlang/OTP天然分布式的能力,相比流行的集群技术如K8S(也叫容器编排),Elixir组建集群更是非常的简单。简单到不需要过多赘述,发布一个多节点的集群只需要几分钟的时间,具体可参考Fly.io这篇配置文档:

Elixir的集群能做到非常细粒度的控制,比如在节点上创建一组Process(此进程并非操作系统级别的进程,而是Erlang VM管理的轻量级用户线程),可以将其通过一致性哈希分布到其他节点的Erlang VM上,目前这种操作在其他编程语言(非Erlang/OTP类)上是无法实现的。具体可参考这些项目的介绍:

Elixir另外一个让分布式集群管理变得简单的能力是其强大的可观测能力。如下是一个集群的Phoenix LiveDashboard:

LiveDashboard可以查看集群的很多信息,比如我们可以查看应用启动后的监督树(Supervisor tree),这是Elixir强大的容错能力来源,如果某个Process出现了Crash的情况,监督树会自动恢复该Process,这也是Erlang/OTP的Let It Crash思想。

进一步我们可以查看某个Process的状态,比如此Process的PID为<0.2243.0>,第一位0表示这是一个本地进程,如果是非0,则是一个远程节点的Process。

当然目前这些还不算啥惊人的部分。惊人的地方在于我们可以与这些Process在线实时交互,比如获取它们当前的状态,给它们发消息获取处理后的状态,这些在排查运行时问题时非常有帮助。比如我远程接入到集群某个节点的Erlang VM中:

如上图,我在接入此节点后,可以与此Process通信并修改它的状态,甚至能找到某个负责与Web端通信的WebSocket的Process,通过此Process可以直接给Web端推送数据。

你可能注意到了上面两个图中Process的PID并不相同,第一个是<42700.16451.14>,第二个是<0.16451.14>,这是因为前者是我在另外一个节点上查看此节点的Process,当然就是一个remote的Process了,后者是我接入了此Process所在的节点。一旦组建好集群,集群内的节点都能很容易获取其他节点的运行时数据。

Elixir版IM集群的例子

上面介绍了Tinode的集群设计,因为Tinode是用Go开发的,它的集群是自己实现的。但用Elixir就不需要这么复杂了,free4chat是我开发的一个开源的基于WebRTC的语音聊天室Web应用,它是基于Elixir开发的,目前服务端是两个节点的集群,上面的截图就是来自它的Elixir后端集群。

free4chat的集群实现非常简单,利用Elixir的强大的集群管理能力,当某个用户通过某个节点接入到某个房间后,该节点从集群全局注册表中查找是否存在该房间的Process,如果不存在就创建,存在就获取该房间的Process,无论该Process是否在本地,用户都可以在该房间与其他人聊天,不同节点Process间的通信由Erlang/OTP自动实现。

总结

Real-time Web开发本身不复杂,复杂的是Real-time叠加分布式带来的难题,当然难题一般只存在同一维度上,当脱离该纬度进入更高维度时,这些难题也许就被轻松解决了。

分布式领域有非常复杂的经典问题,Erlang/OTP在该领域深耕了很多年,很多都有了工业级的解决方案,站在Erlang/OTP巨人肩膀上的Elixir不仅继承了其强大的分布式解决方案,还在多个领域开辟了新的开发体验,比如Phoenix LiveView另辟蹊径带来了新的Web开发体验。

本文没有提到的是,上述的例子并没有考虑数据库的问题,如果再引入传统的数据库,会让问题变得更为复杂。数据库如何能给Realtime Web带来新的开发体验?也许这两篇文章能给我们更多启发: