Stl.Fusion

Stl.Fusion

分布式响应式记忆化的高性能.NET框架

Stl.Fusion是一个创新的.NET框架,通过分布式响应式记忆化技术处理大规模分布式状态。它解决了缓存、实时更新和网络通信等问题,大幅提升应用性能。Fusion简化了实时和离线功能开发,支持Blazor等多平台,并实现高度代码复用。其智能缓存和失效机制为开发高效、可扩展的应用提供了强大支持。

Fusion计算服务实时更新缓存分布式系统Github开源项目

👾 Fusion: 真实存在的"实时开启"开关

构建 覆盖率 NuGet 版本 MIT 许可证 <br/> Discord 服务器 提交活动 下载量

Fusion 是一个 .NET 库,它实现了 🦄 分布式反应式记忆化 (DREAM) – 这是一个新颖的抽象概念,有点类似于 MobX 或 Flux,但设计用于处理跨越后端微服务、API 服务器,甚至延伸到应用程序每个客户端的任意大规模状态

Fusion 用一个工具解决了一系列臭名昭著的难题:

问题所以你不需要...
📇 缓存Redis, memcached, ...
🤹 实时缓存失效没有好的解决方案 - <br/>这是一个臭名昭著的难题
🚀 实时更新SignalR, WebSockets, gRPC, ...
🤬 网络碎片化大量代码
🔌 离线模式支持大量代码
📱 客户端状态管理MobX, Flux/Redux, Recoil, ...
💰 Blazor WebAssembly、Server 和 Hybrid/MAUI 的单一代码库没有好的替代方案

最棒的是:Fusion 为你透明地处理所有这些,所以基于 Fusion 的代码几乎与不涉及它的代码相同。你只需要:

  • 在你的 Fusion 服务上"实现"IComputeService(一个标记接口),以确保在编译时为它生成调用拦截代理。
  • [ComputeMethod] 标记需要"Fusion 行为"的方法 + 将它们声明为 virtual
  • 通过 serviceCollection.AddFusion().AddService<MyService>() 注册服务
  • 像往常一样解析和使用它们 - 即,将它们作为依赖项传递,调用它们的方法等。

当调用 [ComputeMethod] 时,魔法就发生了:

  1. 当 Fusion 知道给定调用的值(考虑 (serviceInstance, method, args...) 缓存键)仍然一致时,Fusion 会立即返回它,而不让方法运行
  2. 当值未缓存或被标记为不一致时,Fusion 让方法运行,但在过程中捕获新值的依赖关系。"依赖关系"是在评估另一个 [ComputeMethod] 调用期间触发的一个 [ComputeMethod] 调用。

第二步允许 Fusion 跟踪当其中一个值改变时,哪些值预期会改变。这与批次可追溯性非常相似,但是针对任意函数而不是制造过程实现的。

拼图的最后一块是 Computed.Invalidate() 块,它允许将缓存结果标记为"与基本事实不一致"。以下是你如何使用它:

var avatars = await GetUserAvatars(userId); using (Computed.Invalidate()) { // 在此块内调用的任何 [ComputeMethod] 都不会正常运行, // 而是使相同调用的结果失效。 // 这些调用同步完成并返回已完成的 Task<TResult>, // 所以你不需要等待它们。 _ = userService.GetUser(userId); foreach (var avatar in avatars) _ = userAvatarService.GetAvatar(userId, avatar.Id); }

失效总是传递性的: 如果 GetUserProfile(3) 调用 GetUserAvatar("3:ava1"),而 GetUserAvatar("3:ava1") 失效,GetUserProfile(3) 也会失效。

为了使其工作,Fusion 维护了一个类似字典的结构,用于跟踪最近和"被观察"的调用结果:

  • 键:(serviceInstance, method, call arguments...)
  • 值:[Computed<T>],它存储结果、一致性状态(ComputingConsistentInvalidated)和依赖-被依赖链接。Computed<T> 实例几乎是不可变的:一旦构造,它们只能转换到 Inconsistent 状态。

你可以这样"提取"某个调用"背后"的 Computed<T> 实例:

var computed1 = await Computed.Capture(() => GetUserProfile(3)); // 你可以等待它的失效: await computed1.WhenInvalidated(); Assert.IsFalse(computed1.IsConsistent()); // 并重新计算它: var computed2 = await computed1.Recompute();

所以任何 Computed<T> 都是可观察的。而且,它可以是远程 Computed<T> 实例的"副本",在你的本地进程中镜像其状态,所以依赖图可以是分布式的。为了实现这一点,Fusion 使用了自己的基于 WebSocket 的 RPC 协议,它与任何其他 RPC 协议非常相似:

  1. 要将调用"发送"到远程对等端,客户端发送"调用"消息
  2. 对等端用"调用结果"消息响应。到目前为止,它与任何其他 RPC 协议没有区别。
  3. 这里是独特的步骤: 对等端可能稍后发送一条消息,告知它之前发送的调用结果已失效。

步骤 3 在网络流量方面并没有改变太多:每次调用最多只有一条额外消息(即最坏情况下是 3 条消息而不是 2 条)。但这个小小的添加允许计算服务客户端准确知道给定的缓存调用结果何时变得不一致。

步骤 3 的存在产生了巨大的差异:任何缓存的且仍然一致的结果都与你从远程服务器获得的数据一样好,对吧?所以在本地解决"命中"这样结果的调用是完全可以的,不会产生网络往返!

最后,任何计算服务客户端的行为都类似于本地计算服务。看看这段代码:

string GetUserName(id) => (await userService.GetUser(id)).Name;

你无法判断这里的 userService 是本地计算服务还是计算服务客户端,对吧?

  • 两个选项都是相同的基本类型(例如 IUserService)。但实现不同:Fusion 服务客户端通过 fusion.AddClient<TInterface>() 注册,而服务器通过 fusion.AddServer<TInterface, TService>() 注册。
  • 而且行为相同:
    • 如果之前的结果仍然一致,你对 userService 的每次调用都会立即终止
    • 如果 GetUserName 是另一个计算服务(本地服务)的方法,支持它所做的 GetUser(id) 调用的计算值会自动扩展 GetUserName(id) 调用的 Fusion 依赖图!

因此,Fusion 抽象了服务的"位置",而且比传统的 RPC 代理做得更好:Fusion 代理默认不会"碎片化"!

文档

<img align="right" width="150" src="https://yellow-cdn.veclightyear.com/835a84d5/b6bb7a4c-a40c-4e1b-be6d-d7b4db9d470c.jpg"/> 如果你喜欢幻灯片,请查看 "为什么实时 Web 应用需要 Blazor 和 Fusion?"演讲 - 它解释了我们解决的许多问题是如何联系在一起的,Fusion 如何解决根本原因,以及如何用 C# 编写 Fusion 关键抽象的简化版本。

幻灯片稍微有些过时 - 例如,现在Fusion客户端使用Stl.Rpc而不是HTTP与服务器通信,但它们涵盖的所有概念仍然完整。

快速入门速查表教程是最好的起点。

查看示例;本文档后面会介绍其中的一些内容。

"你的证据是什么?"<sup><a href="https://www.youtube.com/watch?v=7O-aNYTtx44<">*</a></sup>

**这一切听起来太好了,简直不像是真的,对吧?**这就是为什么在本文档的剩余部分有大量的视觉证明。但如果你在Fusion的源代码或示例中发现任何令人担忧的地方,请随时在Discord上向我们提出质疑!

让我们从一些重量级的内容开始:

看看Actual Chat – 一个由Fusion背后的团队打造的全新聊天应用。

Actual Chat融合了实时音频、实时转录和AI辅助, 让你以最高效率进行沟通。 它拥有WebAssembly、iOS、Android和Windows的客户端, 在这些平台上实现了近100%的代码共享。 除了实时更新,它的一些功能,如离线模式, 都是由Fusion驱动的。

我们在这里发布了一些来自Actual Chat代码库的示例, 加入这个聊天室,了解我们如何在真实应用中使用它。

现在,来看示例:

下面是Fusion+Blazor示例 向3个浏览器窗口提供实时更新:

<img src="https://img.shields.io/badge/-Live!-red" valign="middle"> 立即体验 此示例的在线版本

该示例支持Blazor Server和Blazor WebAssembly 两种托管模式。 即使在不同窗口中使用不同的模式, Fusion仍然能保持共享状态的每一位同步, 包括登录状态:

Fusion快吗?

**是的,它快得令人难以置信。**以下是Actual Chat上最频繁的RPC调用之一的调用持续时间分布:

IChats.GetTile读取一个小的"聊天瓦片" - 通常是固定在特定ID范围内的5个条目,因此可以高效缓存。即使对于这些调用,典型的响应时间也几乎无法测量:每个X轴标记都比前一个大10倍,所以你看到的最高峰值在0.03ms

接下来在约4-5ms处的凸起是服务实际访问数据库的时间 - 也就是说,这是你在没有Fusion的情况下预期看到的时间。不过,负载会高得多,因为你在这个图表上看到的调用是"抵达"服务器的调用 - 换句话说,它们没有被客户端或其Fusion服务消除。

Fusion测试套件中的一个小型综合基准测试 比较了基于"原始"Entity Framework Core的 数据访问层(DAL)与其依赖Fusion的版本:

每秒调用次数PostgreSQLMariaDBSQL ServerSqlite
单个读取器1.02K645.77863.333.79K
960个读取器(高并发)12.96K14.52K16.66K16.50K
单个读取器 + Fusion9.54M9.28M9.05M8.92M
960个读取器 + Fusion145.95M140.29M137.70M141.40M

在Ryzen Threadripper 3960X上运行此测试的原始输出在这里。读取器的数量乍看起来很疯狂,但它是为了最大化DAL的非Fusion版本的输出而调整的(读取器是异步的,所以它们大部分时间都在等待数据库响应)。

Fusion的透明缓存确保你的代码产生的每个API调用结果都被缓存,而且,即使在重新计算这些结果时,它们大多使用其他缓存的依赖项,而不是访问速度慢得多的存储(在这种情况下是数据库)。

有趣的是,即使没有依赖项的"层"(只考虑"零层"),Fusion也能将此测试运行的API调用速度提高8,000到12,000倍。

是什么让Fusion如此快速:

  • 这个概念本身就是为了消除任何不必要的计算。想想msbuild,但是用于你的方法调用结果:已计算且一致的内容永远不会被重新计算。
  • Fusion在内存中缓存调用结果,所以如果命中缓存,它们立即可用。无需往返外部缓存,无需序列化/反序列化等。
  • 此外,也没有克隆:缓存的是调用返回的.NET对象或结构,所以任何调用结果都是"共享的"。这比例如在每次命中时反序列化一个新副本更加CPU缓存友好。
  • Fusion使用自己的Stl.Interception库来拦截方法调用,虽然还没有基准测试,但这些是.NET上可用的最快的调用拦截器 - 它们比如Castle.DynamicProxy提供的拦截器稍快。它们不会装箱调用参数,每次调用只需要1次分配。
  • Stl.Rpc(Fusion负责RPC调用的部分)也是如此。它的初步基准测试结果显示它比SignalR快约1.5倍,比gRPC快约3倍
  • Stl.Rpc使用.NET上可用的最快序列化器 – 默认为MemoryPack(它不需要运行时IL Emit),尽管你也可以使用MessagePack(它稍快,但需要IL Emit)或你喜欢的任何其他序列化器。
  • Fusion中所有关键执行路径都经过大量优化。本页面的存档版本显示,上述测试的性能目前比2年前提高了3倍。

Fusion可扩展吗?

是的。Fusion做的事情类似于任何[MMORPG]游戏引擎所做的:尽管完整的游戏状态是巨大的,但仍然可以为100万+玩家实时运行游戏,因为每个玩家只观察到完整游戏状态的一小部分,因此你只需要确保被观察的部分状态适合内存即可。

这正是Fusion所做的:

  • 它按需生成被观察的状态部分(即当你调用[Compute Service]方法时)
  • 确保支持这部分状态的依赖图在有人使用时保留在内存中
  • 销毁未被观察的部分。

查看教程中的"扩展Fusion服务"部分,了解Fusion如何扩展的更详细描述。

说得够多了。给我看代码!

一个典型的Compute Service如下所示:

public class ExampleService : IComputeService { [ComputeMethod] public virtual async Task<string> GetValue(string key) { // 此方法从非Fusion"源"读取数据, // 因此在写入时需要失效(参见SetValue) return await File.ReadAllTextAsync(_prefix + key); } ```csharp [ComputeMethod] public virtual async Task<string> GetPair(string key1, string key2) { // 此方法只使用其他 [ComputeMethod] 或静态数据, // 因此不需要在写入时进行失效处理 var v1 = await GetNonFusionData(key1); var v2 = await GetNonFusionData(key2); return $"{v1}, {v2}"; } public async Task SetValue(string key, string value) { // 此方法更改了 GetValue 和 GetPair 读取的数据, // 但由于 GetPair 使用了 GetValue,因此一旦我们使 GetValue 失效, // GetPair 也会自动失效。 await File.WriteAllTextAsync(_prefix + key, value); using (Computed.Invalidate()) { // 这是使此方法更改的内容失效的方式。 // 调用参数很重要:您只使具有匹配参数的调用结果失效, // 而不是每个 GetValue 调用的结果! _ = GetValue(key); } } }

[ComputeMethod] 表示每次调用此方法时,其结果都由 [Computed Value] 支持,因此它在运行时捕获依赖关系,并在当前计算值仍然一致时立即返回结果。

计算服务的注册方式几乎类似于单例:

var services = new ServiceCollection(); var fusion = services.AddFusion(); // 可以多次调用 // ~ 类似于 service.AddSingleton<[TService, ]TImplementation>() fusion.AddService<ExampleService>();

查看 HelloBlazorServer 示例 中的 CounterService 以了解计算服务的实际代码。

现在,我猜你很好奇使用 Fusion 的 UI 代码是什么样的。你会惊讶地发现,它简单得不能再简单了:

// MomentsAgoBadge.razor @inherits ComputedStateComponent<string> @inject IFusionTime _fusionTime <span>@State.Value</span> @code { [Parameter] public DateTime Value { get; set; } protected override Task<string> ComputeState() => _fusionTime.GetMomentsAgo(Value) ; }

MomentsAgoBadge 是一个 Blazor 组件,用于显示 "N [秒/分钟/...] 前" 的字符串。上面的代码与其 实际代码 几乎完全相同,只是后者由于处理 null 而稍微复杂一些。

你可以看到它使用了 IFusionTime - 这是一个内置的计算服务,提供 GetUtcNowGetMomentsAgo 方法。你可能猜到了,这些方法的结果会自动失效;查看 FusionTime 服务 以了解其工作原理。

但这里重要的是 MomentsAgoBadge 继承自 ComputedStateComponent<T> - 这是一个抽象类型,提供了 ComputeState 方法。你可能猜到了,这个方法的行为类似于 [Compute Method]。

ComputedStateComponent<T> 暴露了 State 属性(类型为 ComputedState<T>),允许你通过其 Value 属性获取 ComputeState() 的最新输出。"State" 是 Fusion 的另一个关键抽象 - 它实现了一个"等待失效并重新计算"循环,类似于这个

var computed = await Computed.Capture(_ => service.Method(...)); while (true) { await computed.WhenInvalidated(); computed = await computed.Update(); }

唯一的区别是它以更健壮的方式执行此操作 - 特别是,它允许你控制失效和更新之间的延迟,访问最新的非错误值等。

最后,ComputedStateComponent 在其 State 更新时自动调用 StateHasChanged(),以确保显示新值。

因此,如果你使用 Fusion,你就不需要在 UI 中编写任何反应代码。 反应(即部分更新和重新渲染)会自动发生,这是因为依赖链将 UI 组件与它们使用的数据提供者连接起来,而数据提供者又与它们使用的数据提供者相连,以此类推 - 直到最基本的"成分提供者",即在变化时失效的计算方法。

如果你想看到更多类似简单的 UI 组件示例,可以查看:

为什么 Fusion 对实时应用来说是一个游戏改变者?

实时通常意味着你使用事件向每个可能受此变化影响的客户端传递变更通知,因此你必须:

  1. 知道要通知哪些客户端关于特定事件。 单单这一点就是一个相当困难的问题 - 特别是,你需要知道每个客户端现在"看到"什么。发送当前不在"视口"中的事件(例如,你可能看到但现在没有看到的帖子)是没有意义的,因为这是一种巨大的浪费,严重限制了可扩展性。与 [MMORPG] 类似,对于大多数网络应用来说,"可见"部分的状态与"可用"部分相比也是很小的。
  2. 将事件应用到客户端状态。 这也是一个相对简单的问题,但请注意,你也应该在服务器端执行相同的操作,而且在两个完全不同的处理程序中保持每个事件的逻辑同步是未来潜在问题的源头。
  3. 使 UI 在每次客户端状态变化时正确更新其事件订阅。 这是客户端代码必须做的事情,以确保第 1 点在服务器端正常工作。同样,这在纸面上看起来是一个可以解决的问题,但如果你想确保你的 UI 提供真正最终一致的视图,事情会变得更加复杂。只需思考一下你会以什么顺序运行"查询初始数据"和"订阅后续事件"这两个操作,就能看出其中的一些问题。
  4. 降低某些事件的速率(例如,每个热门帖子的"点赞"事件)。 在纸面上看起来很容易,但如果你想确保用户看到系统的最终一致视图,事情会变得更复杂。 特别是,这意味着你发送的每个事件都要"总结"它和你丢弃的每个事件所做的更改,所以可能 你需要为每个这样的事件设计专用的类型、生产者和处理程序。

而 Fusion 使用单一的抽象来解决所有这些问题,允许它自动识别和跟踪数据依赖关系。

为什么 Fusion 对具有复杂 UI 的 Blazor 应用来说是一个游戏改变者?

Fusion 允许你创建真正独立的 UI 组件。 你可以将它们嵌入到 UI 的任何部分,而不需要担心它们如何相互交互。

这使得 Fusion 非常适合 Blazor 上的微前端: 在这里,创建松耦合的 UI 组件的能力至关重要。

除此之外,如果你的失效逻辑正确, Fusion 保证你的 UI 状态最终是一致的。

你可能认为所有这些只在 Blazor Server 模式下工作。 但事实并非如此,所有这些 UI 组件在 Blazor WebAssembly 模式下也能工作,这是 Fusion 提供的另一个独特功能。 任何 [Compute Service] 都可以替换为 [Compute Service Client],它不仅仅是代理调用,还完全 消除了你在常规客户端代理中可能遇到的频繁通信问题。

下一步

文章和其他内容

附言: 如果你已经花时间了解了Fusion, 请帮助我们改进它,完成Fusion反馈表 (1…3分钟)。

编辑推荐精选

扣子-AI办公

扣子-AI办公

职场AI,就用扣子

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

堆友

堆友

多风格AI绘画神器

堆友平台由阿里巴巴设计团队创建,作为一款AI驱动的设计工具,专为设计师提供一站式增长服务。功能覆盖海量3D素材、AI绘画、实时渲染以及专业抠图,显著提升设计品质和效率。平台不仅提供工具,还是一个促进创意交流和个人发展的空间,界面友好,适合所有级别的设计师和创意工作者。

图像生成热门AI工具AI图像AI反应堆AI工具箱AI绘画GOAI艺术字堆友相机
码上飞

码上飞

零代码AI应用开发平台

零代码AI应用开发平台,用户只需一句话简单描述需求,AI能自动生成小程序、APP或H5网页应用,无需编写代码。

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具使用教程AI营销产品酷表ChatExcelAI智能客服
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

热门AI工具生产力协作转型TraeAI IDE
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

数据安全AI助手热门AI工具AI辅助写作AI论文工具论文写作智能生成大纲
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

热门AI工具AI办公办公工具智能排版AI生成PPT博思AIPPT海量精品模板AI创作
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

下拉加载更多