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

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

时间:2025-01-17 10:34:08浏览次数:1  
标签:Monitor 4000 代码 连接数 争用 死锁 线程 多线程

背景

我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统。陆陆续续开发了几年,从一开始的偶有用户尝试,到如今的 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 字段,每创建该类的一个实例时它都递增一次,使用的代码是 objCt++; 此操作要求将 objCt 的值加载到寄存器中,使该值递增,然后将其存储到 objCt 中。

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

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

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

静态成员和静态构造函数

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

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

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

建议

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

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

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

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

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

  • 锁定实例时要谨慎。 如果应用程序中不属于该类型的其他代码锁定了该对象,则会发生死锁。

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

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

类库相关建议

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

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

  • 默认情况下使静态数据是线程安全的。

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

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


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

标签:Monitor,4000,代码,连接数,争用,死锁,线程,多线程
From: https://www.cnblogs.com/sheng_chao/p/18676420

相关文章

  • 11 多线程详解
    线程简介程序:程序就像一堆写好的指令和数据放在一起,它是静止的,不会自己动起来。进程(Process):进程是把程序真正运行起来的过程,它是动态的,系统会给它分配各种资源,比如内存等。线程(Thread):一个进程里通常会有好几个线程,最少也得有一个,不然进程就没啥用了。线程是CPU安排干活和实际......
  • 27. 多线程技术
    一、多线程技术  QThread类是PySide中的核心线程类,要实现一个线程,需要创建QThread类的有一个子类,并且实现其run()方法。  线程也有自己自己的生命周期,其中包含5种状态,分别为:新建状态、就绪状态、运行状态、阻塞状态**和死亡状态。新建状态就是线程被创建时的状......
  • Java多进程多线程处理详解
    在Java编程中,多进程和多线程是两种常见的并发编程技术,用于提高程序的执行效率和响应速度。本文将详细介绍Java中的多进程和多线程处理,包括理论概述和代码示例。通过本文,你将了解如何在Java中实现多进程和多线程,以及它们在实际应用中的价值和意义。一、理论概述1.多进程与多线程......
  • 多线程同步与任务完成等待机制总结
    多线程同步与任务完成等待机制总结在多线程编程中,合理的同步机制能够有效地协调多个线程之间的执行顺序,确保任务按照预期执行。常见的同步机制包括CountDownLatch、CyclicBarrier、CompletableFuture、ExecutorService.invokeAll()和Phaser。接下来,我们将通过具体场景加......
  • c语言——【linux】多线程编程 (内附练习及代码)
    1:开启一个线程主线程中:使用标准IO,向一个文件中写入任意数据分支线程:使用标准IO,读取该文件中的数据#include<stdio.h>#include<string.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<p......
  • 多线程的实现
    1.线程和进程的概述1.1什么是进程?正在执行的程序,称为进程。进程是系统分配资源的单元。现在系统支持多进程的。1.2什么是线程?线程,又称轻量级进程(LightWeightProcess)。线程是进程中的一条执行路径,也是CPU的基本调度单位。若一个程序可同一时间执行多个线程,就是支......
  • 进程、线程,java如何实现多线程任务,Thread里面常用的方法
    1.线程和进程的概述1.1什么是进程正在执行的程序,称为进程。进程是系统分配资源的单元。1.2什么是线程线程又称轻量级的进程,是CPU调度的基本单位,一个进程由一个或多个线程组成进程与进程之间无法共享数据同一个进程的线程与线程之间可以共享数据2.并发与并行的概述并......
  • Qt多线程开发全解析
     目录一、引言二、Qt多线程基础概念2.1线程与进程2.2Qt中的线程类三、Qt多线程的使用场景3.1耗时操作处理3.2实时数据处理3.3多任务并发执行四、Qt多线程的实现方式4.1继承QThread类4.2使用QThreadPool线程池4.3利用QtConcurrent框架五、线程间的同步......
  • 书生大模型实战营L2G4000 - InternVL 部署微调实践
    1.多模态大模型的工作原理和设计模式1.1工作原理多模态大模型的工作原理基于深度学习技术,旨在通过处理和融合多种模态数据(如文本、图像、音频、视频等)来实现更全面的理解和生成能力。以下是其工作原理的核心要点:1.数据预处理与编码数据预处理:不同模态的数据需要特定的预......
  • Python 魔法学院 - 第18篇:Python 多线程 ⭐⭐⭐
    目录引言1.多线程编程基础1.1什么是多线程?1.2为什么需要多线程?1.3Python中的多线程模块2.创建和启动线程2.1使用`threading.Thread`创建线程2.2使用`target`参数创建线程3.线程同步3.1为什么需要线程同步?3.2使用`Lock`实现线程同步3.3使用`RLock`......