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

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

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

https://kf.shengxunwei.com/


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

本篇介绍 PLINQ 并行查询技术。

并行 LINQ (PLINQ) 是语言集成查询 (LINQ) 模式的并行实现。 PLINQ 将整套 LINQ 标准查询运算符实现为 System.Linq 命名空间的扩展方法,并提供适用于并行操作的其他运算符。 PLINQ 将 LINQ 语法的简洁和可靠性与并行编程的强大功能结合在一起。

什么是并行查询

一个 PLINQ 查询的许多方面都类似于非并行的 LINQ to Objects 查询。 与顺序 LINQ 查询一样,PLINQ 查询对任何内存中 IEnumerable 或 IEnumerable 数据源执行操作,并且推迟了执行,即在枚举查询前不会开始执行。 主要区别在于,PLINQ 会尝试充分利用系统上的所有处理器。 方法是将数据源分区成片段,然后在多个处理器上针对单独工作线程上的每个片段执行并行查询。 在许多情况下,并行执行意味着查询运行速度显著提高。

通过并行执行,通常只需向数据源添加 AsParallel 查询操作,PLINQ 即可显著提升性能(与某些类型查询的旧代码相比)。 但是,并行可能会引入其自身的复杂性,因此并非所有的查询操作的运行速度在 PLINQ 中都更快。 事实上,并行实际上会降低某些查询的速度。 因此,应了解排序等问题将如何对并行查询产生影响。

PLINQ 查询性能的影响因素

下面各部分列出了并行查询性能的一些最重要的影响因素。 这些都是一般性说明,本身并不足以用于在所有情况下预测查询性能。

  1. 整体工作的计算成本。

    为了实现加速,PLINQ 查询必须有足够多的适合并行操作来抵消开销。 工作可以表示为每个委托的计算成本乘以源集合中的元素数量。 假设操作可以并行执行,它的计算成本越高,加速的机会就越大。 例如,如果函数的执行时间为 1 毫秒,那么超过 1000 个元素的顺序查询需要 1 秒的时间才能执行此操作,而在四核计算机上,并行查询可能只需要 250 毫秒就能完成。 这就产生 750 毫秒的加速。 如果函数执行每个元素需要 1 秒,就会产生 750 秒的加速。 如果委托成本很高,PLINQ 可能会让速度显著提升,前提是源集合中只有几项。 相反,包含最简单的委托的小型源集合通常不适合执行 PLINQ。

    在下面的示例中,queryA 可能很适合执行 PLINQ,前提是它的 Select 函数涉及很多工作。 queryB 可能不适合执行 PLINQ,因为 Select 语句中没有足够多的工作,并行开销会抵消大部分或全部加速。
var queryA = from num in numberList.AsParallel()
select ExpensiveFunction(num); //good for PLINQ var queryB = from num in numberList.AsParallel()
where num % 2 > 0
select num; //not as good for PLINQ
  1. 系统上的逻辑内核数量(并行度)。

    这一点是上一部分的必然结果,在具有更多内核的计算机上,适合并行查询运行得更快,这是因为可以在更多并发线程之间划分工作。 加速总量取决于查询整体工作的并行度百分比。 不过,不要认为所有查询在八核计算机上的运行速度都是在四核计算机上的两倍。 优化查询以实现最佳性能时,请务必在具有不同数量内核的计算机上度量实际结果。 这一点与第 1 点相关:需要更大的数据集,才能利用更多的计算资源。

  2. 操作的数量和种类。

    如果有必要维护源序列中的元素顺序,PLINQ 提供 AsOrdered 运算符。 虽然排序有相关成本,但此成本通常还算低。 GroupBy 和 Join 操作同样也会产生开销。 如果允许按任意顺序处理源集合中的元素,并在准备就绪后立即将它们传递给下一个运算符,PLINQ 的性能最佳。

  3. 查询执行形式。

    若要通过调用 ToArray 或 ToList 存储查询结果,所有并行线程的结果都必须合并到一个数据结构中。 这就涉及不可避免的计算成本。 同样,如果使用 foreach(Visual Basic 中的 For Each)循环来循环访问结果,工作线程的结果必须串行化到枚举器线程。 不过,如果只想根据每个线程的结果执行某操作,可以使用 ForAll 方法对多个线程执行此操作。

  4. 合并选项类型。

    PLINQ 可以配置为缓冲输出并在生成整个结果集后分块区生成或一次性全部生成,也可以配置为在各个结果生成时流式传输它们。 前一个导致总体执行时间减少,后一个导致所生成元素之间的延迟减少。 尽管合并选项不一定会对总体查询性能造成重大影响,但它们可能会影响感知性能,因为它们控制用户在看到结果前必须等待的时间。

选择使用模型

var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
where num % 2 == 0
select num;
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count());
// The example displays the following output:
// 5000 even numbers out of 10000 total

AsParallel 扩展方法将后续查询运算符(在此示例中为 where 和 select)绑定到 System.Linq.ParallelEnumerable 实现。

执行模式

默认情况下,PLINQ 是保守的。 在运行时,PLINQ 基础结构将分析查询的总体结构。 如果通过并行可能会提高查询速度,PLINQ 则将源序列分区为可以同时运行的任务。 如果并行化查询不安全,PLINQ 则只会按顺序运行查询。 如果 PLINQ 可以在可能会较昂贵的并行算法或成本较低的顺序算法之间进行选择,它会默认选择顺序算法。 可以使用 WithExecutionMode 方法和 System.Linq.ParallelExecutionMode 枚举指示 PLINQ 选择并行算法。 如果你通过测试和测量知道特定查询以并行方式执行得更快时,此做法非常有用。

并行度

默认情况下,PLINQ 使用主机计算机上的所有处理器。 可以使用 WithDegreeOfParallelism 方法指示 PLINQ 使用不超过指定数量的处理器。 当你要确保计算机上运行的其他进程收到一定的 CPU 时间量时,此做法将非常有用。 下面的片段将查询限制为最多使用两个处理器。

var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
where Compute(item) > 42
select item;

在查询要执行大量非受计算限制的工作(如文件 I/O)的情况下,最好指定比计算机上的内核数要大的并行度。

已排序和未排序的并行查询

在某些查询中,一个查询运算符必须产生保留源序列排序的结果。 为此,PLINQ 提供了 AsOrdered 运算符。 AsOrdered 不同于 AsSequential。 尽管仍并行处理 AsOrdered 序列,但会缓冲和排序它的结果。 由于顺序暂留通常涉及额外的工作,因此处理 AsOrdered 序列可能比处理默认 AsUnordered 序列更慢。 特定的已排序并行操作是否比操作的顺序版本更快取决于许多因素。

下面的代码示例演示了如何选择使用顺序保留。

var evenNums =
from num in numbers.AsParallel().AsOrdered()
where num % 2 == 0
select num;

并行和顺序查询

某些操作要求按顺序提供源数据。 必要时,ParallelEnumerable 查询运算符自动还原为顺序模式。 对于要求顺序执行的用户定义的查询运算符和用户委托,PLINQ 提供了 AsSequential 方法。 使用 AsSequential 时,查询中的所有后续运算符都会顺序执行,直到再次调用 AsParallel。

异常

当一个 PLINQ 查询执行时,可能会同时从不同的线程引发多个异常。 此外,处理异常的代码可能与引发异常的代码处于不同的线程上。 PLINQ 使用 AggregateException 类型封装查询抛出的所有异常,并将这些异常封送回调用线程。 在调用线程上,只需要一个 try-catch 块。 不过,可以循环访问在 AggregateException 中封装的所有异常,并捕获任何可以安全恢复的异常。 在极少数情况下,可能会抛出一些未在 AggregateException 中包装、ThreadAbortException 也没有进行包装的异常。

如果允许异常向上冒泡回到联接线程,则查询也许可以在引发异常后继续处理一些项。

自定义分区程序

在某些情况下,可以通过编写利用源数据的某些特征的自定义分区程序来提高查询性能。 在查询中,自定义分区程序本身是被查询的可枚举对象。

int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(SomeFunction);

PLINQ 支持固定数量的分区(尽管在运行时期间为了负载均衡可能会将数据重新动态分配到这些分区)。 For 和 ForEach 仅支持动态分区。也就是说,分区数在运行时发生变化。

ForAll 运算符

在顺序 LINQ 查询中,执行一直延迟到在 foreach(Visual Basic 中为 For Each)循环中或通过调用 ToList、ToArray 或 ToDictionary 等方法枚举查询。 在 PLINQ 中,还可以使用 foreach 执行查询以及循环访问结果。 但是,foreach 本身不会并行运行,因此,它要求将所有并行任务的输出合并回该循环正在上面运行的线程中。 在 PLINQ 中,在必须保留查询结果的最终排序,以及以按串行方式处理结果时,例如当为每个元素调用 Console.WriteLine 时,则可以使用 foreach。 为了在无需顺序暂留以及可自行并行处理结果时更快地执行查询,请使用 ForAll 方法执行 PLINQ 查询。 ForAll 不执行最终的这一合并步骤。 下面的代码示例说明如何使用 ForAll 方法。 此处使用 System.Collections.Concurrent.ConcurrentBag 是因为它已优化,可以同时添加多个线程,而无需尝试移除任何项。

var nums = Enumerable.Range(10, 10000);
var query =
from num in nums.AsParallel()
where num % 10 == 0
select num; // Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));

衡量 PLINQ 性能

在很多情况下,可以并行化查询,但是设置并行查询的开销可能会超出获得的性能收益。 如果查询不执行大量的计算,或者如果数据源较小,则 PLINQ 查询的速度可能比顺序 LINQ to Objects 查询的速度慢。 可以在 Visual Studio Team Server 中使用并行性能分析器比较各种查询的性能,查找处理瓶颈,以及确定查询是并行运行还是按顺序运行。


简介

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

https://kf.shengxunwei.com/

  • 可以追踪正在访问网站或使用 APP 的所有访客,收集他们的浏览情况,使客服能够主动出击,施展话术,促进成单。

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

升讯威在线客服系统的并发高性能数据处理技术:PLINQ并行查询技术的更多相关文章

  1. .net core 和 WPF 开发升讯威在线客服系统【私有化部署免费版】发布

    希望 .net 和 WPF 技术时至今日,还能有一些存在感. 这个项目源于2015年前后,当时开发的初版,我使用了 ASP.NET MVC 做为后端,数据库使用原生 ADO.NET 进行操作.WPF ...

  2. Linux + .net core 开发升讯威在线客服系统:首个经过实际验证的高性能版本

    业余时间用 .net core 写了一个在线客服系统.并在博客园写了一个系列的文章,写介绍这个开发过程: .net core 和 WPF 开发升讯威在线客服系统:目录 https://blog.she ...

  3. .net core 和 WPF 开发升讯威在线客服系统:把 .Net Framework 打包进安装程序

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 系列文章目录: https://blog.shengxunwei.com/Home/Post/44a3 ...

  4. .net core 和 WPF 开发升讯威在线客服系统:使用本地IP数据库实现访客来源快速定位,支持国外

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程.本产品已经成熟稳定并投入商用. 免费使用 & 私有化部署:https://kf.shengxun ...

  5. 开发升讯威在线客服系统启示录:怎样编写堪比 MSDN 的用户手册

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 免费在线使用 & 免费私有化部署:https://kf.shengxunwei.com 文章目 ...

  6. 1个程序员单干之:怎样给我的升讯威在线客服系统编写堪比 MSDN 的用户手册

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 免费在线使用 & 免费私有化部署:https://kf.shengxunwei.com 视频实 ...

  7. 详解升讯威在线客服系统前端 JavaScript 脚本加密技术(1)

    我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户.对我来说,只要能获得用户的认可,就是我最大的动力. 这段时间有几个技术小伙伴问了我一个有意思的问题:"你的前端脚本是 ...

  8. .net core 和 WPF 开发升讯威在线客服系统:怎样实现拔网线也不丢消息的高可靠通信(附视频)

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程.本产品已经成熟稳定并投入商用. 在线演示环境:https://kf.shengxunwei.com 注意 ...

  9. Linux + .net core 开发升讯威在线客服系统:同时支持 SQL Server 和 MySQL 的实现方法

    前段时间我发表了一系列文章,开始介绍基于 .net core 的在线客服系统开发过程. 有很多朋友一直提出希望能够支持 MySQL 数据库,考虑到已经有朋友在用 SQL Server,我在升级的过程中 ...

  10. .net core 和 WPF 开发升讯威在线客服系统:调用百度翻译接口实现实时自动翻译

    业余时间用 .net core 写了一个在线客服系统.并在博客园写了一个系列的文章,写介绍这个开发过程. 我把这款业余时间写的小系统丢在网上,陆续有人找我要私有化版本,我都给了,毕竟软件业的初衷就是免 ...

随机推荐

  1. Java中序列化和反序列化解释

    在Java中,序列化(Serialization)是指将对象的状态转换为字节流的过程,以便将其保存到文件.在网络中传输或持久化到数据库中.而反序列化(Deserialization)则是将字节流转换回 ...

  2. 从源码角度剖析 golang 如何fork一个进程

    从源码角度剖析 golang 如何fork一个进程 创建一个新进程分为两个步骤,一个是fork系统调用,一个是execve 系统调用,fork调用会复用父进程的堆栈,而execve直接覆盖当前进程的堆 ...

  3. Dapr在Java中的实践 之 环境准备

    Dapr简介 Dapr (Distributed Application Runtime)是一个可移植的.事件驱动的运行时,它使任何开发人员都可以轻松地构建运行在云和边缘上的弹性.无状态和有状态的应用 ...

  4. Kafka关键原理

    日志分段切分条件 日志分段文件切分包含以下4个条件,满足其一即可: 当前日志分段文件的大小超过了broker端参数 log.segment.bytes 配置的值.log.segment.bytes参数 ...

  5. List转为Map

    List转为Map 1.业务需求,需要将List<SysSetting>转为Map SysSetting是一个对象 @Data @TableName("t_sys_setting ...

  6. C++容器(vector、deque、list、map)

    (1) vector:将元素置于一个动态数组中,可以随机存储元素(也就是用索引直接存取). 数组尾部添加或删除元素非常迅速.但在中部或头部就比较费时. *代码演示:* 取: at在下标越界时会抛出异常 ...

  7. 前端vue uni-app百度地图定位组件,显示地图定位,标记点,并显示详细地址

    快速实现前端百度地图定位组件,显示地图定位,标记点,并显示详细地址; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12677 ...

  8. Linux系统运维之负载均衡Tengine

    一.介绍 Tengine是由淘宝网发起的Web服务器项目.它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性.Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到 ...

  9. P7561[JOISC 2021 Day2] 道路の建設案 (Road Construction) 题解

    P7561[JOISC 2021 Day2] 道路の建設案 (Road Construction) 题解 题目描述 JOI 国是一个 \(x\times y\) 的二维平面,王国里有 \(n\) 个城 ...

  10. 如何不加锁地将数据并发写入Apache Hudi?

    最近一位 Hudi 用户询问他们是否可以在不需要任何锁的情况下同时从多个写入端写入单个 Hudi 表. 他们场景是一个不可变的工作负载. 一般来说对于任何多写入端功能,Hudi 建议启用锁定配置. 但 ...