.Net中的并行编程-5.流水线模型实战

时间:2023-07-04 10:13:38

自己在Excel整理了很多想写的话题,但苦于最近比较忙(其实这是借口)。。。。

上篇文章《.Net中的并行编程-4.实现高性能异步队列》介绍了异步队列的实现,本篇文章介绍我实际工作者遇到了处理多线程问题及基于异步队列底层数据结构的解决方案。

需求如下:1.提供数据服写入服务供上层应用调用,数据写入服务处理的吞吐量要达到60w/s每秒,也就是用户每秒发送60w的数据然后通过数据写入服务写到数据库中(数据库为公司自主研发的实时数据库)。

2.尽量简化上层应用调用服务的复杂度。

一、分析性能瓶颈

1.实时数据库要求传入的数据需要带有ID(类似于关系库中的自增主键),但用户只传Name,需要根据Name去实时数据找到对应的数据ID,所以该地方频繁的与数据库交互是最大的瓶颈。

2.由于数据库原始的.Net API是基于底层C++ API 进行的二次开发,需要.Net和C++编程模型之间的转换(比如.Ne中存数数据用的是List,C++是数组,所以要把.Net中的List对象 转换为c++中的数组对象),而这个转换耗时相当大,所以数据转换操作也是性能瓶颈之一。

二 、解决方案

1.对于性能瓶颈1可以使用本地缓存,本地缓存维护了name和数据库ID直接的映射关系,所以查找id时,只要去本地缓存查找即可。由于该缓存读远远大于写,所以缓存部分使用了普通的Dictionary加上读写锁,读取数据时只要获取读锁,跟新数据时加上写锁,所以设计出以下流程。

.Net中的并行编程-5.流水线模型实战

2.对于性能瓶颈2可以采用并行的方式解决,我们并行模型主要分为三种:数据并行,任务并行,和流水线并行,关于这三种模型的基本介绍可以参照《Intel Threading Building Blocks 编程指南》第二章 并行思维。有的以上基础理论的支持,所以设计出以下模型。

.Net中的并行编程-5.流水线模型实战

上图中,每个操作都是在单独的线程中进行的,操作与操作之间采用队列进行解耦。由于流水线模型是典型的空间换时间,每个操作之间累积了大量数据,所以一旦程序出现崩溃,那么导致队列内的数据全部丢失,所以还要保证系统的高容错,由于采用了上篇文章中的队列而且在开始编码的时候已经考虑到了系统异常处理的细节,所以异常处理和多线程编程的复杂度也大大降低,并且每个操作处理的线程数也可动态适配(其实最理想的情况是根据每个操作的处理能力以及CPU利用率等进行动态负载均衡,由于配置的线程数已尽满足了需求所以动态负载均衡没有进行开发)。

三、关于扩展性

如果未来数据量要增加,那么相应的处理能力也要增加。那么最简单的方案就是动态增加每个处理操作的线程数,由于操作之间采用队列进行通信,那必然会设计到锁的问题,而且同时的进行多线程入队和多线程出队会造成频繁的上下文切换并造成性能下降,所以为了避免这种多线程竞争了情况,可采用多流水线,每个流水线的流程都跟上图一样,对于流水线直之间的负载均衡可以采用加权平均轮训,哈希求余等算法。

补充:

流水线模型的总体时间取决于最耗时的操作环节,所以再采用流水线模型时要尽量减少最耗时操作环节的时间,如在本案例中最耗时的环节是写入数据库部分,所以在进行编码和设计时对该部分进行了重点优化。