升讯威在线客服系统的并发高性能数据处理技术:超强的 SignalR

我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户。对我来说,只要能获得用户的认可,就是我最大的动力。

最近客服系统成功经受住了客户现场组织的压力测试 ,获得了客户的认可。

客户组织多名客服上线后,所有员工同一时间 打开访客页面疯狂不停 的给在线客服发消息,系统稳定无异常无掉线 ,客服回复消息正常。消息实时到达无任何延迟。
https://kf.shengxunwei.com/


我会通过一系列的文章详细分析升讯威在线客服系统的并发高性能技术是如何实现的,使用了哪些方案以及具体的做法。

本篇介绍超强的 SignalR 技术。

什么是 SignalR?

ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。

适合 SignalR 的候选项:

  • 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
  • 仪表板和监视应用。 示例包括公司仪表板、即时销售更新或旅行警报。
  • 协作应用。 协作应用的示例包括白板应用和团队会议软件。
  • 需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。
  • SignalR 提供用于创建服务器到客户端远程过程调用 (RPC) 的 API。 RPC 从服务器端 .NET Core 代码调用客户端上的函数。 提供多个受支持的平台,其中每个平台都有各自的客户端 SDK。 因此,RPC 调用所调用的编程语言有所不同。

以下是 ASP.NET Core SignalR 的一些功能:

  • 自动处理连接管理。
  • 同时向所有连接的客户端发送消息。 例如聊天室。
  • 向特定客户端或客户端组发送消息。
  • 对其进行缩放,以处理不断增加的流量。

传输

SignalR 支持以下用于处理实时通信的技术(按正常回退的顺序):

  • WebSockets
  • Server-Sent Events
  • 长轮询

SignalR 自动选择服务器和客户端能力范围内的最佳传输方法。

中心

SignalR 使用中心在客户端和服务器之间进行通信。

Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。 可以将强类型参数传递给方法,从而支持模型绑定。 SignalR 提供两种内置中心协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。 与 JSON 相比,MessagePack 通常会创建更小的消息。 旧版浏览器必须支持 XHR 级别 2 才能提供 MessagePack 协议支持。

中心通过发送包含客户端方法的名称和参数的消息来调用客户端代码。 作为方法参数发送的对象使用配置的协议进行反序列化。 客户端尝试将名称与客户端代码中的方法匹配。 当客户端找到匹配项时,它会调用该方法并将反序列化的参数数据传递给它。

在升讯威客服系统中使用 SignalR

创建 SignalR 中心

中心是一个类,用作处理客户端 - 服务器通信的高级管道。

在 SignalRChat 项目文件夹中,创建 Hubs 文件夹。

在 Hubs 文件夹中,使用以下代码创建 ChatHub 类:

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}

ChatHub 类继承自 SignalRHub。 Hub 类管理连接、组和消息。

可通过已连接客户端调用 SendMessage,以向所有客户端发送消息。 本教程后面部分将显示调用该方法的 JavaScript 客户端代码。 SignalR 代码是异步模式,可提供最大的可伸缩性。

配置 SignalR

必须将 SignalR 服务器配置为将 SignalR 请求传递给 SignalR。 将以下突出显示的代码添加到 Program.cs 文件。

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

以上突出显示的代码将 SignalR 添加到 ASP.NET Core 依赖关系注入和路由系统。

添加 SignalR 客户端代码

  • 创建并启动连接。

  • 向"提交"按钮添加一个用于向中心发送消息的处理程序。

  • 向连接对象添加一个用于从中心接收消息并将其添加到列表的处理程序。

    "use strict";

    var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

    //Disable the send button until connection is established.
    document.getElementById("sendButton").disabled = true;

    connection.on("ReceiveMessage", function (user, message) {
    var li = document.createElement("li");
    document.getElementById("messagesList").appendChild(li);
    // We can assign user-supplied strings to an element's textContent because it
    // is not interpreted as markup. If you're assigning in any other way, you
    // should be aware of possible script injection concerns.
    li.textContent = ${user} says ${message};
    });

    connection.start().then(function () {
    document.getElementById("sendButton").disabled = false;
    }).catch(function (err) {
    return console.error(err.toString());
    });

    document.getElementById("sendButton").addEventListener("click", function (event) {
    var user = document.getElementById("userInput").value;
    var message = document.getElementById("messageInput").value;
    connection.invoke("SendMessage", user, message).catch(function (err) {
    return console.error(err.toString());
    });
    event.preventDefault();
    });

当从 Hub 类外部调用客户端方法时,没有与该调用关联的调用方。 因此,无法访问 ConnectionId、Caller 和 Others 属性。

需要将用户映射到连接 ID 并保留该映射的应用可以执行以下操作之一:

  • 将单个或多个连接的映射保留为组。 有关详细信息,请参阅SignalR 中的组。
  • 通过单一实例服务保留连接和用户信息。 有关详细信息,请参阅将服务注入中心。 单一实例服务可以使用任何存储方法,例如:
  • 字典中的内存中存储。
  • 永久性外部存储。 例如,使用 Azure.Data.Tables NuGet 包的数据库或 Azure 表存储。
  • 在客户端之间传递连接 ID。

从 IHost 获取 IHubContext 实例

从 Web 主机访问 IHubContext 对于与 ASP.NET Core 之外的区域集成很有用,例如,使用第三方依赖项注入框架:

public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            var hubContext = host.Services.GetService(typeof(IHubContext<ChatHub>));
            host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder => {
                    webBuilder.UseStartup<Startup>();
                });
    }

SignalR 中的单个用户可以与一个应用建立多个连接。 例如,用户可以在桌面和手机上进行连接。 每台设备都有一个单独的 SignalR 连接,但它们都与同一个用户关联。 如果向用户发送消息,则与该用户关联的所有连接都会收到消息。 可以通过中心内的 Context.UserIdentifier 属性访问连接的用户标识符。

默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 若要自定义此行为,请参阅使用声明自定义标识处理。

重新连接时不会保留组成员身份。 重新建立连接后,需要重新加入组。 无法计算组的成员数,因为如果将应用程序扩展到多台服务器,则无法获取此信息。

若要在使用组时保护对资源的访问,请使用 ASP.NET Core 中的身份验证和授权功能。 如果仅当凭据对组有效时才将用户添加到该组,则发送到该组的消息将仅发送给授权用户。 但是,组不是一项安全功能。 身份验证声明具有组不具备的功能,例如到期和撤销。 如果撤销用户对组的访问权限,应用必须从组中显式删除该用户。


简介

升讯威在线客服与营销系统是一款客服软件,但更重要的是一款营销利器。

https://kf.shengxunwei.com/

  • 可以追踪正在访问网站或使用 APP 的所有访客,收集他们的浏览情况,使客服能够主动出击,施展话术,促进成单。
    访* 客端在 PC 支持所有新老浏览器。包括不支持 WebSocket 的 IE8 也能正常使用。
  • 移动端支持所有手机浏览器、APP、各大平台的公众号对接。
  • 支持访客信息互通,可传输访客标识、名称和其它任意信息到客服系统。
  • 具备一线专业技术水平,网络中断,拔掉网线,手机飞行模式,不丢消息。同类软件可以按视频方式对比测试。