Fusion 是一个 .NET 库,它实现了 🦄 分布式反应式记忆化 (DREAM) – 这是一个新颖的抽象概念,有点类似于 MobX 或 Flux,但设计用于处理跨越后端微服务、API 服务器,甚至延伸到应用程序每个客户端的任意大规模状态。
Fusion 用一个工具解决了一系列臭名昭著的难题:
问题 | 所以你不需要... |
---|---|
📇 缓存 | Redis, memcached, ... |
🤹 实时缓存失效 | 没有好的解决方案 - <br/>这是一个臭名昭著的难题 |
🚀 实时更新 | SignalR, WebSockets, gRPC, ... |
🤬 网络碎片化 | 大量代码 |
🔌 离线模式支持 | 大量代码 |
📱 客户端状态管理 | MobX, Flux/Redux, Recoil, ... |
💰 Blazor WebAssembly、Server 和 Hybrid/MAUI 的单一代码库 | 没有好的替代方案 |
最棒的是:Fusion 为你透明地处理所有这些,所以基于 Fusion 的代码几乎与不涉及它的代码相同。你只需要:
IComputeService
(一个标记接口),以确保在编译时为它生成调用拦截代理。[ComputeMethod]
标记需要"Fusion 行为"的方法 + 将它们声明为 virtual
serviceCollection.AddFusion().AddService<MyService>()
注册服务当调用 [ComputeMethod]
时,魔法就发生了:
(serviceInstance, method, args...)
缓存键)仍然一致时,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...)
Computing
、Consistent
、Invalidated
)和依赖-被依赖链接。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 协议非常相似:
步骤 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与服务器通信,但它们涵盖的所有概念仍然完整。
查看示例;本文档后面会介绍其中的一些内容。
**这一切听起来太好了,简直不像是真的,对吧?**这就是为什么在本文档的剩余部分有大量的视觉证明。但如果你在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仍然能保持共享状态的每一位同步, 包括登录状态:
**是的,它快得令人难以置信。**以下是Actual Chat上最频繁的RPC调用之一的调用持续时间分布:
IChats.GetTile
读取一个小的"聊天瓦片" - 通常是固定在特定ID范围内的5个条目,因此可以高效缓存。即使对于这些调用,典型的响应时间也几乎无法测量:每个X轴标记都比前一个大10倍,所以你看到的最高峰值在0.03ms
!
接下来在约4-5ms
处的凸起是服务实际访问数据库的时间 - 也就是说,这是你在没有Fusion的情况下预期看到的时间。不过,负载会高得多,因为你在这个图表上看到的调用是"抵达"服务器的调用 - 换句话说,它们没有被客户端或其Fusion服务消除。
Fusion测试套件中的一个小型综合基准测试 比较了基于"原始"Entity Framework Core的 数据访问层(DAL)与其依赖Fusion的版本:
每秒调用次数 | PostgreSQL | MariaDB | SQL Server | Sqlite |
---|---|---|---|---|
单个读取器 | 1.02K | 645.77 | 863.33 | 3.79K |
960个读取器(高并发) | 12.96K | 14.52K | 16.66K | 16.50K |
单个读取器 + Fusion | 9.54M | 9.28M | 9.05M | 8.92M |
960个读取器 + Fusion | 145.95M | 140.29M | 137.70M | 141.40M |
在Ryzen Threadripper 3960X上运行此测试的原始输出在这里。读取器的数量乍看起来很疯狂,但它是为了最大化DAL的非Fusion版本的输出而调整的(读取器是异步的,所以它们大部分时间都在等待数据库响应)。
Fusion的透明缓存确保你的代码产生的每个API调用结果都被缓存,而且,即使在重新计算这些结果时,它们大多使用其他缓存的依赖项,而不是访问速度慢得多的存储(在这种情况下是数据库)。
有趣的是,即使没有依赖项的"层"(只考虑"零层"),Fusion也能将此测试运行的API调用速度提高8,000到12,000倍。
msbuild
,但是用于你的方法调用结果:已计算且一致的内容永远不会被重新计算。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做的事情类似于任何[MMORPG]游戏引擎所做的:尽管完整的游戏状态是巨大的,但仍然可以为100万+玩家实时运行游戏,因为每个玩家只观察到完整游戏状态的一小部分,因此你只需要确保被观察的部分状态适合内存即可。
这正是Fusion所做的:
查看教程中的"扩展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
- 这是一个内置的计算服务,提供 GetUtcNow
和 GetMomentsAgo
方法。你可能猜到了,这些方法的结果会自动失效;查看 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 使用单一的抽象来解决所有这些问题,允许它自动识别和跟踪数据依赖关系。
Fusion 允许你创建真正独立的 UI 组件。 你可以将它们嵌入到 UI 的任何部分,而不需要担心它们如何相互交互。
这使得 Fusion 非常适合 Blazor 上的微前端: 在这里,创建松耦合的 UI 组件的能力至关重要。
除此之外,如果你的失效逻辑正确, Fusion 保证你的 UI 状态最终是一致的。
你可能认为所有这些只在 Blazor Server 模式下工作。 但事实并非如此,所有这些 UI 组件在 Blazor WebAssembly 模式下也能工作,这是 Fusion 提供的另一个独特功能。 任何 [Compute Service] 都可以替换为 [Compute Service Client],它不仅仅是代理调用,还完全 消除了你在常规客户端代理中可能遇到的频繁通信问题。
附言: 如果你已经花时间了解了Fusion, 请帮助我们改进它,完成Fusion反馈表 (1…3分钟)。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。
openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号