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分钟)。

编辑推荐精选

Trae

Trae

字节跳动发布的AI编程神器IDE

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

热门AI工具生产力协作转型TraeAI IDE
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

聊天机器人AI助手热门AI工具AI对话
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

热门AI工具AI办公办公工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

模型训练热门AI工具内容创作智能问答AI开发讯飞星火大模型多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

AI助手热门AI工具AI创作AI辅助写作讯飞绘文内容运营个性化文章多平台分发
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

下拉加载更多