NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

SignalR Core 尝鲜

A developer's tour of SignalR Core Alpha

  • 2018-03-19
  • 本文字数:5153 字

    阅读完需:约 17 分钟

要点

  • SignalR Core 改用 Microsoft.AspNetCore.Sockets,不再依赖 HTTP。
  • 使用 MessagePack 序列化格式,支持二进制协议。
  • TypeScript 客户端移除了第三方依赖包。
  • 支持 WebSocket 原生客户端,可以使用自己构建的客户端连接到 SignalR 服务器。
  • 伸缩方式更灵活,可以通过自己实现的方式进行横向扩展。

几个月前,SignalR Core 团队发布了一个非官方版本的ASP.NET Core SignalR 。为此,开发人员有机会了解其工作原理以及ASP.NET SignalR 与Signal Core 新架构之间的区别。

SignalR Core 中移除了哪些特性

通过对比两个版本的 SignalR 可以发现,新版本不再支持一些重要的特性。首先是移除了对 jQuery 和其他第三方类库的依赖,因为新版本的 JavaScript 客户端是使用 TypeScript 开发的。其次是自动连接后的消息重放功能,移除该功能主要是出于性能方面的考虑。服务器需要为每一个连接维护一个缓冲区,用于保存消息,以便后续重新发送。当客户端断开连接,可以尝试重新恢复连接,然后将未发送的消息发送给客户端。可以想象,如果有很多客户端断开连接,而且每个客户端都发送大量的消息,对于服务器来说是个很大的负担。另一个被 SignalR 团队移除特性是多 Hub 端点,所以,在新版本里,每个连接只有一个 Hub。

新版本的 SignalR Core 不再支持横向扩展(Scale Out)模型,原因是 MessageBus 被当成了横向扩展的“万灵丹”,但它实际上只支持 Azure Service Bus 、Redis 和 SQL Server。在实际的协作场景当中(客户端到客户端),随着客户端和消息数量的增长,通过以上三种方式进行横向扩展会有瓶颈问题。

不过,我认为,移除横向扩展功能这一决定有点太过激进,因为在某些场景下,MessageBus 仍然十分有用。例如,在将 SignalR 作为一个广播服务器时,它可以控制发送消息的数量。而在 SignalR Core 的 alpha 版本中,开发者可以根据实际情况选择是否进行横向扩展,如业务需求、系统约束或基础设施,这种设计更加“可插拔”。SignalR Core 团队提供了一个使用 Redis 进行横向扩展的示例。其他扩展方式可能会被包含在 SignalR Core 的最终版中。

最后一个被移除的功能是多服务器间的双向复制(backplane),因为这个功能会在服务器场生成太多的流量。ASP.NET SignalR 通过 MessageBus 在服务器间复制每一个消息,因为客户端无法直接连接到服务器场,而现在,SignalR 使用粘性会话来避免在所有服务器间复制消息。这样一来,SignalR Core 就可以知道哪个客户端连接到了哪台服务器上。

SignalR Core 中增加了哪些新特性

现在让我们来看一下 SignalR Core 带来了哪些新的特性。首先是使用了二进制协议来发送和接收消息。在 ASP.NET SignalR 中只能使用 JSON 格式的文本来发送和接收消息,而现在则可以使用二进制协议,该二进制协议基于 MessagePack 序列化格式,比 JSON 更快、体积更小。

主机无关性是另一个非常重要的特性,有了这个特性,就可以移除对 HTTP 的依赖。现在,我们可以在 HTTP 或 TCP 之上使用 SignalR。端点 API 也是非常重要的一个特性,它是基础的构建块,用于支持主机无关性。因为新版本是基于 Microsoft.AspNetCore.Sockets 这一底层的网络抽象层,所以可以直接使用 Socket。这么说来,SignalR Hub 其实也就是一个端点。

多格式也是一个很酷的特性,有了这个特性,我们可以处理任意格式的消息。我们可以使用多种不同的客户端连接到同一个端点,这些客户端可以使用不同的消息格式。也就是说,SignalR Core 已经实现了消息的格式无关性。这个示例在同一个端点上使用了三种格式(JSON、PIPE 和Protobuf)来读写消息,因为使用了自定义的处理器,可以无缝地处理各种格式的消息。正如之前提到的那样,可能是因为使用了Microsoft.AspNetCore.Sockets,从底层来看,消息都只是简单的二进制字节。

新版本还支持WebSocket 原生客户端,所以开发者也可以使用除SignalR Web 客户端之外的其他客户端。之前,开发者必须使用基于JavaScript 的Web 客户端连接到SignalR 服务器上。现在,开发者可以自己开发客户端,充分利用浏览器API 提供的优势。当然,开发者也可以使用最新的TypeScript 客户端,因为TypeScript 提供了很多有用的特性。另外,客户端是通过NPM 包管理器进行发布的,这样依赖管理也就变得更简单了。

最后一点是,横向扩展变得更灵活,提供了更高的可扩展性。SignalR Core 团队简化并改进了横向扩展模型,并提供了一个基于Redis 的横向扩展示例,帮助开发者了解如何进行横向扩展。

去年9 月14 号,SignalR Core 团队发布了第一个alpha 版,10 月9 号发布了第二个alpha 版,也就是SignalR Core 2.0 官方预览版。现在,我们即将探讨这一版本中包含的主要变化。

在得知有新版本后,我在第一时间去拉取代码,并试着去构建最新的代码。不过,正如预期的那样,因为代码还在开发当中,无法立马构建成功。尽管如此,我们还是能够在第一时间看到正在发生的变更,这有助于我们了解为什么要做出这些变更。接下来,我将列出我在构建过程中遇到的问题,并告诉大家我做了哪些事情来修复这些问题。

要在项目中使用SignalR Core,必须引用Microsoft.AspNetCore.SignalR,最新版本是1.0.0-alpha2-final。

HubConnectionBuilder

在之前的版本中,如果要在服务器端连接到一个 Hub,我们会使用 HubConnection 类,比如:

var connection = new HubConnection(new Uri(baseUrl), loggerFactory);而现在,我们需要使用 HubConnectionBuilder 类(实现了 Builder 设计模式)来连接 SignalR Core Hub,这也是第一个导致代码构建失败的变化。这一变化让建立连接变得更具可扩展性,不需要使用满是参数或带有 null 参数的构造函数。我很喜欢这个变化,因为它简化了建立连接的过程。

复制代码
var connection = new HubConnectionBuilder()
                        .WithUrl(baseUrl)
                        .WithConsoleLogger()
                        .Build();

在服务器端处理连接

在之前的版本中,客户端在“On”方法中处理由 SignalR Hub 广播过来的数据,这个时候需要处理一大堆参数:

复制代码
connection.On("UpdateCatalog", new[] { typeof(IEnumerable<Product>) }, data =>
{
    var products = data[0] as List<Product>;
    foreach (var item in products)
    {
        Console.WriteLine($"{item.name}: {item.quantity}");
    }
});

可以看出,这个方法有点累赘,因为不管用不用得到方法里的参数,都必须指定这些参数和它们的类型,即使用不到,也要指定一个空数组。但问题是,处理器的参数是无类型的,所以,即使在数组中指定了类型,仍然需要遍历数组,对它们进行类型转换。

而新版本的 SignalR Core 提供了最新的泛型方法重载机制,通过这种方式指定参数类型后就不需要再进行类型转换。泛型方法对原始方法进行了包装,从而简化了开发者的工作。最终的代码更简单、可读性更好。

复制代码
connection.On<List<Product>>("UpdateCatalog", products =>
{
    // now, “products” parameter is a List<Product> type.
    foreach (var item in products)
    {
        Console.WriteLine($"{item.name}: {item.quantity}");
    }
});

命名约定

我发现 Invoke 方法名发生了变化(这个变化也导致代码构建失败):

await connection.Invoke("RegisterProduct", cts.Token, product, quanity);这是一个异步方法,为了遵循命名约定,方法名被改成了 InvokeAsync,方法参数的顺序也发生了改变,令牌参数被放在了最后:

await connection.InvokeAsync("RegisterProduct", product, quanity, cts.Token);因为遵循了命名约定和标准,开发者在使用 SignalR Core API(包括 SignalR Core 的开发者团队)时就更加直观,因为它带来了代码的统一性。例如,如果开发者在他们的 IDE 中使用了 Intellisense,就可以提前知道这个方法是异步的。

另一个与命名约定有关的变化是 MapEndpoint 方法,这个方法被改成了 MapEndPoint,遵循了 Pascal 的大小写风格

之前:

复制代码
app.UseSockets(routes =>
{
    routes.MapEndpoint<MessagesEndPoint>("/message");
});

现在:

复制代码
app.UseSockets(routes =>
{
    routes.MapEndPoint<MessagesEndPoint>("message");
});

可以看到,现在不使用“/”符号了。MapHub 方法也一样。不过,我们发现这里存在一个问题,这些方法没有使用 PathString API。不过,在下一个版本中会继续使用“/”,与其他的.Net Core API 保持一致。

命名变更

命名方面发生了很多变更,其中一个是与 Connection 类有关的 ConnectionContext。ConnectionContext 包含了与连接相关的信息,如元数据、通道等。

之前:

public override async Task OnConnectedAsync(Connection connection)现在:

public override async Task OnConnectedAsync(ConnectionContext connection)另一个命名方面的变更与 ConnectionContext 中的 Transport 有关。之前,用于管理输入和输出的属性分别叫作 Input 和 Output,而现在它们被改为 In 和 Out。

之前:

复制代码
connection.Transport.Input.WaitToReadAsync()
connection.Transport.Output.WriteAsync()

现在:

复制代码
connection.Transport.In.WaitToReadAsync()
connection.Transport.Out.WriteAsync()

TryRead 和 WriteAsync

TryRead 和 WriteAsync 方法得到了简化。之前,它们接收一个 Message 对象作为参数。

之前:

复制代码
Message message;
if (connection.Transport.Input.TryRead(out message))
{
    ...
}
connection.Transport.Output.WriteAsync(new Message(payload, format, endOfMessage));

现在:

复制代码
// message is byte[]
if (connection.Transport.In.TryRead(out var message))
{
    ...
}
// payload is byte[]
connection.Transport.Out.WriteAsync(payload);

现在他们使用字节数组作为参数,因为底层的 Socket 使用了 Channel<byte[]>。SignalR Core 团队认为,将字节数据移到上层可以让 Socket 层的逻辑更清晰。之前,SignalR Core 团队在字节数据之上使用了一个底层的数据帧协议(不过 WebSocket 已经有数据帧,所以没有在 WebSocket 上使用该协议)。

因此,Microsoft.AspNetCore.Sockets 层得到了“净化”,只允许端点处理二进制数据,而端点就可以使用任何一种协议,比如 TCP 或 HTTP。

底层的数据帧协议是在 Microsoft.AspNetCore.SignalR 层实现的,所以消息类型、数据帧都是在实现了 IHubProtocol 接口的类中处理的,比如 JsonHubProtocol 和 MessagPackHubProtocol。这种设计提供了一种可扩展的方式用于实现其他的 Hub 协议。

其他变更

我们可以直接通过 NPM 管理器来安装 signalr-client,比如,我在 package.json 文件里将它作为客户端依赖:

复制代码
{
  "version": "1.0.0",
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "@aspnet/signalr-client": "^1.0.0-alpha2-final",
    "jQuery.tabulator": "^1.12.0"
  }
}

Visual Studio 会在构建解决方案时自动安装这个包。当然,我们也可以使用.NET Core 内置的新特性,它会自动把 signalr-client 文件拷贝到 wwwroot 目录,这样就不需要再使用 gulp、grunt 或其他任务执行器了。

复制代码
[
    {
        "outputFileName": "wwwroot/lib/signalr/signalr-clientES5-1.0.0-alpha2-final.min.js",
        "inputFiles": [
            "node_modules/@aspnet/signalr-client/dist/browser/signalr-clientES5-1.0.0-alpha2-final.min.js"
        ],
        "minify": {
            "enabled": false
        }
    },
    {
        "outputFileName": "wwwroot/lib/signalr/signalr-client-1.0.0-alpha2-final.min.js",
        "inputFiles": [
            "node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.min.js"
        ],
        "minify": {
            "enabled": false
        }
    }
]

默认情况下,.NET Core 启用 minify 选项,而我引用的文件已经被 minify 过,当它尝试再次 minify 这些文件时就会报错,于是我就把 minify 选项禁用了。

结论

以上就是我在升级到最新版 SignalR Core 时发现的一些变化。我把它们分享出来,让其他开发者也知道这些变更以及为什么要做出这些变更。我希望这些信息对大家有用,也鼓励大家在自己的项目中测试最新的 SignalR Core。

我花了几个小时解决在构建新版本代码时遇到的问题,而查看代码和理解这些变更又额外花了我几个小时时间,不过这些都是值得的。

关于作者

Geovanny Alzate Sandoval 是一名来自哥伦比亚麦德林的系统工程师,他喜欢所有与软件开发、新技术、设计模式和软件架构相关的事物。他已经在该领域工作了十多年,做过开发者、技术负责人和软件架构师。他乐于向社区做贡献,喜欢在博客上写与微软新技术有关的东西。另外,他还是麦德林.NET 开发者社区 MDE.NET 的联合组织者。

查看英文原文: First Hand Account of SignalR Core

2018-03-19 17:572151

评论

发布
暂无评论
发现更多内容

TiDB Parser模块的简单解读与改造方法

TiDB 社区干货传送门

TiDB 底层架构

还在用变量去实现多维度分组排序吗?你 out 了!

TiDB 社区干货传送门

实践案例

Weir:原生 TiDB 支持的数据库中间件

TiDB 社区干货传送门

实践案例

TiDB new feature max_execution_time

TiDB 社区干货传送门

038-拯救大兵瑞恩之 TiDB 如何在 TiKV 损坏的情况下恢复

TiDB 社区干货传送门

TiDB 在汽车之家818台网互动项目中的应用

TiDB 社区干货传送门

实践案例 管理与运维 数据库架构选型

TiDB 升级——ansible与tiup使用小结

TiDB 社区干货传送门

TiDB 底层架构

从 MySQL 大量数据清洗到 TiDB 说起

TiDB 社区干货传送门

实践案例

Flink on TiDB —— 便捷可靠的实时数据业务支撑

TiDB 社区干货传送门

实践案例

MySQL 与 TiDB 不同的 DDL 发展历程

TiDB 社区干货传送门

TiDB 底层架构

TiDB 数据一致性校验实现:Sync-diff-inspector 优化方案

TiDB 社区干货传送门

性能调优

当数据库遇上 Kuberbetes丨「能量钛」圆桌论坛回顾

TiDB 社区干货传送门

实践案例 数据库架构选型

DM filter 实践整理

TiDB 社区干货传送门

实践案例

知乎已读服务的前世今生与未来

TiDB 社区干货传送门

【精选实践】一体化无边界的大数据基础平台

TiDB 社区干货传送门

DM多库合并至TiDB

TiDB 社区干货传送门

迁移 实践案例

TiDB 慢日志在伴鱼的实践

TiDB 社区干货传送门

实践案例

TiDB Ansible 使用指南

TiDB 社区干货传送门

TiDB 热点问题详解

TiDB 社区干货传送门

TiDB 集群可用性增强 —— TiDB 5.0 的 Joint Consensus 机制介绍

TiDB 社区干货传送门

TiDB 底层架构

事务前沿研究丨确定性事务

TiDB 社区干货传送门

TiDB 底层架构

在 minikube 上使用 TiDB Operator 构建 TiDB 集群(持续更新中)

TiDB 社区干货传送门

安装 & 部署

一张脑图让你快速了解 TiDB 5.0版本新特性

TiDB 社区干货传送门

TiDB 底层架构

接触TiDB4.0时,一些部署方式实践尝试

TiDB 社区干货传送门

安装 & 部署

PD 调度器模块

TiDB 社区干货传送门

TiDB 底层架构

TiDB 的统计信息

TiDB 社区干货传送门

TiDB 3.0:窗口函数初体验

TiDB 社区干货传送门

PD 启动主流程分析

TiDB 社区干货传送门

TiDB 底层架构

Grafana汇总报表

TiDB 社区干货传送门

监控

TiDB 热点问题定位

TiDB 社区干货传送门

故障排查/诊断

一篇文章带你玩转 TiDB 灾难恢复

TiDB 社区干货传送门

故障排查/诊断

SignalR Core尝鲜_.NET_Geovanny Alzate Sandoval_InfoQ精选文章