在线客服系统 QPS 突破 240/秒,连接数突破 4000,日请求数接近1000万次,.NET 多线程技术的高性能实践

news/2025/1/17 10:24:28/文章来源:https://www.cnblogs.com/sheng_chao/p/18676420

背景

我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统。陆陆续续开发了几年,从一开始的偶有用户尝试,到如今的 QPS 突破 240 次/秒,连接数突破 4000,日请求数接近 1000 万。(PS:阿里云真贵啊

在这篇文章中,我将简要介绍我在技术上做了哪些工作,我是如何做到的。

PS:

虽然在线使用是免费的,有条件的用户也可以自行下载私有化部署包,安装部署在自己的服务器使用。(https://kf.shengxunwei.com/)

我做的是个什么产品

先简单看一下这款 QPS 突破 240 次/秒,日请求数接近 1000 万的个人独立产品,它是个啥?直接上图,是一款在线客服系统:

https://kf.shengxunwei.com/
100% 免费下载私有化部署,希望能够打造: 开放、开源、共享。努力打造 .net 社区的一款优秀开源产品。

我是如何做到高 QPS 的?

深入 .NET 多线程技术

死锁和争用条件

多线程处理解决了吞吐量和响应性问题,但引入此功能会带来新的问题:死锁和争用条件。

死锁

两个线程中的每一个线程都尝试锁定另外一个线程已锁定的资源时,就会发生死锁。 两个线程都不能继续执行。

托管线程处理类的许多方法都提供了超时设定,有助于检测死锁。 例如,下面的代码尝试在 lockObject 对象上获取锁。 如果在 300 毫秒内没有获取锁,Monitor.TryEnter 返回 false。

if (Monitor.TryEnter(lockObject, 300)) {  try {  // Place code protected by the Monitor here.  }  finally {  Monitor.Exit(lockObject);  }  
}  
else {  // Code to execute if the attempt times out.  
}  

争用条件
争用条件是程序的结果取决于两个或更多个线程中的哪一个先到达某一特定代码块时出现的一种 bug。 多次运行程序会产生不同的结果,并且无法预测任何给定运行的结果。

争用条件的一个简单例子是递增一个字段。 假定某个类有一个私有 static 字段(在 Visual Basic 中为 Shared),每创建该类的一个实例时它都递增一次,使用的代码是 objCt++; (C#) 或 objCt += 1 (Visual Basic)。 此操作要求将 objCt 的值加载到寄存器中,使该值递增,然后将其存储到 objCt 中。

在多线程应用程序中,一个已加载并递增该值的线程可能会被另一个线程抢先,抢先的线程执行全部三个步骤;第一个线程继续执行并存储其值时,它会覆盖 objCt,但不考虑该值在其暂停执行期间已更改这一事实。

通过使用 Interlocked 类的方法(如 Interlocked.Increment),可以轻松避免这种争用条件。 若要了解在多个线程间同步数据的其他技巧,请参阅为多线程处理同步数据。

争用条件也可能会在同步多个线程的活动时发生。 编写每一行代码时,都必须考虑出现以下情况时会发生什么情况:一个线程在执行该行代码(或构成该行的任何机器指令)前,其他线程抢先执行了该代码。

静态成员和静态构造函数

在类的类构造函数(C# 中为 static 构造函数、Visual Basic 中为 Shared Sub New)完成运行之前,该类不会初始化。 为防止对未初始化的类型执行代码,在类构造函数完成运行之前,公共语言运行时会禁止从其他线程到类的 static 成员(在 Visual Basic 中为 Shared 成员)的所有调用。

例如,如果某个类构造函数启动了一个新线程,并且该线程过程调用了该类的 static 成员,则在该类构造函数完成之前,会一直禁止新线程。

以上情况适用于可拥有 static 构造函数的任意类型。

建议

使用多线程时需考虑以下准则:

  • 不要使用 Thread.Abort 终止其他线程。 对另一个线程调用 Abort 无异于引发该线程的异常,也不知道该线程已处理到哪个位置。

  • 不要使用 Thread.Suspend 和 Thread.Resume 同步多个线程的活动。 请使用 Mutex、ManualResetEvent、AutoResetEvent 和 Monitor。

  • 不要从主程序中控制工作线程的执行(如使用事件)。 而应设计程序,使工作线程负责等待任务可用,然后执行任务,并在完成时通知程序的其他部分。 如果不阻止工作线程,请考虑使用线程池线程。 Monitor.PulseAll 非常适用于阻止工作线程。

  • 不要将类型用作锁定对象。 也就是说,避免一些代码,或避免使用 Monitor.Enter 和 Type 对象。 对于给定类型,每个应用域只有一个 System.Type 实例。 如果锁定对象的类型是“公共的”,那么不属于自己的代码也能锁定该对象,从而导致死锁。 有关其他问题,请参阅可靠性最佳做法。

  • 锁定实例时要谨慎,例如,C# 中的 lock(this) 或 Visual Basic 中的 SyncLock(Me)。 如果应用程序中不属于该类型的其他代码锁定了该对象,则会发生死锁。

  • 务必确保已进入监视器的线程始终离开该监视器,即使线程在监视器中时发生异常也是如此。 C# 的 lock 语句和 Visual Basic 的 SyncLock 语句可自动提供此行为,同时使用 finally 块来确保调用 Monitor.Exit。 如果无法确保调用 Exit,请考虑将设计更改为使用 Mutex。 Mutex 在当前拥有它的线程终止后会自动释放。

  • 务必针对需要不同资源的任务使用多线程,避免向单个资源指定多个线程。 例如,任何涉及 I/O 的任务都会从其拥有自己的线程这一点得到好处,因为此线程在 I/O 操作期间将阻止,从而允许其他线程执行。 用户输入是另一种可从专用线程获益的资源。 在单处理器计算机上,涉及大量计算的任务可与用户输入和涉及 I/O 的任务并存,但多个计算量大的任务将相互竞争。

类库相关建议

为多线程处理设计类库时,考虑以下准则:

  • 如果可能,避免同步需求。 对于大量使用的代码更应如此。 例如,可以将一个算法调整为容忍争用情况,而不是完全消除争用情况。 不必要的同步会降低性能,并且可能导致出现死锁和争用情况。

  • 默认情况下使静态数据(在 Visual Basic 中为 Shared)是线程安全的。

  • 默认情况下不要使实例数据是线程安全的。 通过添加锁来创建线程安全代码会降低性能、加剧锁争用情况,并且可能导致出现死锁。 在常见应用程序模型中,一次只有一个线程执行用户代码,从而最大限度降低线程安全性的需求。 出于此原因,.NET 类库在默认情况下不是线程安全的类库。

  • 避免提供可更改静态状态的静态方法。 在常见服务器方案中,静态状态可在各个请求之间共享,这意味着多个线程可同时执行该代码。 这可能导致线程出现 bug。 请考虑使用一种设计模式,将数据封装到在各请求之间不共享的实例中。 此外,如果同步静态数据,更改状态的静态方法间的调用可导致死锁或冗余同步,进而降低性能。


钟意的话请给个赞支持一下吧,谢谢~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/870672.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

极空间使用clouddrive2 docker挂载115(SSH版)

极空间开通SSH了,因此可以用clouddrive2将115挂载到极空间并在“个人空间”中看到了。 按照官方教程,用docker-compose或者docker cli命令进行部署即可。 具体部署步骤极空间打开SSH(系统设置-远程协助/SSH)。 使用SSH工具如XTerminal等进入SSH,端口为开启SSH时设置的端口…

CentOS7.8安装k8s.210708

1, 安装 docker / kubelet # 在 master 节点和 worker 节点都要执行 # 最后一个参数 1.20.6 用于指定 kubenetes 版本,支持所有 1.20.x 版本的安装 # 腾讯云 docker hub 镜像 # export REGISTRY_MIRROR="https://mirror.ccs.tencentyun.com" # DaoCloud 镜像 # e…

docker中修改wordpress上传文件大小.210709

进入docker docker exec -it wordpress /bin/bash root@1d8a4fbdaa6b:/var/www/html# cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini root@1d8a4fbdaa6b:/var/www/html# cd /usr/local/etc/php root@1d8a4fbdaa6b:/usr/local/etc/php# apt-get update …

数字孪生建筑智慧运维系统

在智慧城市的建设浪潮中,数字孪生技术以其独特的优势,成为推动城市智慧化发展的重要力量。数字孪生建筑智慧运维系统,通过构建建筑的数字副本,实现对建筑全生命周期的实时监控、分析和优化,为建筑运维提供决策支持,提高建筑的能效和安全性,降低运维成本。 建设内容阐述1…

主机防护如何更安全、高效? HSS新增多种特性,让你少走弯路

华为云企业主机安全12月新版本现已上线!此次更新重点针对RASP检测、CI/CD安全防护、容器防逃逸、资产基线检测进行了优化增强。 阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手…

域密码到期发送提醒邮件的超简单方法.210715

1,AD服务器下载安装免费的卓豪AD管理工具https://www.manageengine.cn/products/self-service-password/free-password-expiry-notification-tool.html 2,设置邮箱3,设置提醒邮件内容,选择域4,愉快的玩耍吧。.zstitle { width: 280px; text-align: center; font-size: 26p…

域控域用户密码过期日期时间更改.210720

1,打开服务管理器,点工具,选择Active Directory 管理中心2,右键域名(本地)-属性3,选择属性编辑器,把maxPwdAge 从90天改成180天。.zstitle { width: 280px; text-align: center; font-size: 26px } .zsimgweixin { width: 280px } .zsimgali { width: 280px; padding: 0px…

面试必考:秒杀系统要如何设计?

前言 高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。 秒杀一般出现在商城的促销活动中,指定了一定数量(比如:10个)的商品(比如:手机),以极低的价格(比如:0.1元),让大量用…

掌握设计模式--观察者模式

观察者模式(Observer Pattern) 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象间的一对多依赖关系,使得当一个对象状态发生改变时,所有依赖于它的对象都会自动收到通知并更新。 主要组成部分 主题(Subject):主题是被观察的对象,它维护一个观察者列表…

[docker逃逸] Privileged 特权模式逃逸复现

本文作者CVE-柠檬i CSDN:https://blog.csdn.net/weixin_49125123 博客园:https://www.cnblogs.com/CVE-Lemon 微信公众号:Lemon安全 简述 在 Docker 中,Privileged 特权模式赋予容器几乎与宿主机相同的权限,允许其访问所有设备和内核功能。这种模式虽然提供了灵活性,但也…

GaussDB实时分析组件

云原生数据库以OLTP为主,同时也支持基于OLTP数据的OLAP需求,如每日报表。在云原生数据库中,DBA可以选择为这部分表创建列存索引。创建完列存索引之后,执行器在做顺序扫描的时候,会自动选择列存索引进行数据的读取,实现快速扫描计算的能力。 云原生数据库以行存为基础,数…

GaussDB关键技术方案_通信组件

GaussDB关键技术方案_通信组件 云原生数据库采用shared disk架构,各个计算节点对等,计算节点之间通过页面交换实现缓存数据的一致性,为了提高页面传递的效率,需要利用RDMA或UB单边读写的能力;云原生数据库为了管理动态资源,需要对动态资源的owner分配进行加锁,分布式锁管…