.Net中 编写 非同步WebAPI 到底有何好处?
本人萌新一枚,实际工作中编写了一些WebApi介面,最近在学习一些非同步和WebApi的深层次的知识,发现很多博客或者说git中的项目出现了非同步WebApi,可能我见识短见,最近才发现, 类似于这样的
public async Task& GetUser(int id)
{ IUserService userService = new UserService(); User user = await userService.QueryByID(id);return Ok(user);
},非同步编程也了解一些,就是不知道这样相比于同步的写法有哪些好处?是能够提高服务吞吐量吗?更直白点说就是我伺服器一秒钟只能处理1000个,现在能处理2000个?是这样吗?
这个代码中,userService获取值如果是直接从内存获取,那么它并不会给带来性能提升,还有可能带来性能损耗,如果它是从redis或资料库获取,那么会得到一定的提升。
非同步是一种很高效的操作,能够尽力榨干机器的性能,明白它的原理就很简单了。
比如现在有十个请求,每个请求分成三个阶段处理,初始化10ms,读取资料库10ms,运算10ms。
假如有一台伺服器,一次只能处理1个请求,那么同时来十个请求,排入队列,1号请求要等到回复要30毫秒,2号回复要等1号完成再进行,那就需要60ms,以此类推,需要300毫秒完成。
如果是非同步,第一个请求进入读取资料库时,CPU就空闲出来了,那么第二个请求的运算就可以开始了。
可以注意到当第一个请求第一阶段运算完成,读资料库时,CPU已经空闲下来了,但是同步的话就只能让它空著。
如果采用非同步,第一个请求30毫秒完成,第二个第40毫秒时就可以完成,第三个请求50ms…十个请求只需要120ms就可以完成。
可以看到,十个请求,120ms就处理ms了本来要300ms才能处理好的任务,时间节省了一半多。
nodejs单线程能够有极高的性能,也是通过非同步来实现的。
性能的提升意味著你可以用更加便宜的硬体节省成本以及应对各种业务需要。
所以,非同步的适用于充分利用资源空闲,反之,如果是不会出现资源空闲的任务,比如上述场景的十个请求都仅仅是读取资料库的话,是毫无意义的。
然后又会有疑问了,通常的web伺服器,它同时能处理多个请求,非同步是否还是需要的?
设想一个场景,同时来了一万个请求,而伺服器比如在Java中直接开同步线程,如果开上几千个就会不堪重负,并且会占用相当大的内存空间,并且显然这几千个线程仅仅是挤出来了计算空间,而对于共享资源比如读写资料库,线程需要进行互相加锁处理。
如果有每个进程,需要先读写10ms资料库,然后计算10ms,再读写资料库10ms,那么你将需要让它持有资料库资源高达30ms,其中有10ms只能眼睁睁看著,一万个请求将浪费10万ms用于等待。
如果能让线程们能够自己切换,它们就像帝国时代里的工人们,砍树的也能采石头,也能造房子,这个问题就能解决。
将任务进行分割,计算的任务,读资料库的任务,进行分离,然后一堆线程轮流负责专门读写资料库,一堆线程轮流专门负责计算,哪里有空闲时,闲的一堆线程过去负责其他工作。
听起来是不是很耳熟,这就是使用iocp或是epoll实现高性能处理的关键,写这些东西很繁琐,调度听起来也有点复杂,放到十多年前,会用这些的,光是跟人口沫横飞都可以吹上个大半天,而且大多数人即使知道了原理,也很难将它们运用好。
到现在,这些完全不用操心了,只需要简单的async与await,调调下线程池参数,必要时再用用自旋锁,遵循好规范,轻易就可以实现高性能方案。
先说结论,使用async(非同步)确实能提升webapi的处理能力。但是具体提升多少,或者使用不当反而会造成性能下降?这个就要好好了解async和sync的异同,然后根据自己的业务和代码逻辑进行选择。而不是盲目的全部都使用async。
通常,async的特性是允许任务在处理的过程中切换线程。这也就意味著,请求处理开始时候使用的一个线程,往往返回的时候是另外一个线程。而sync(同步)自始自终都会在保持在同一个线程上。
.net程序在运行时,会负责调度一个线程池,其中的线程数量,跟cpu、操作系统和.net版本不同数量不同,大概在cpu核心数~32768之间。例如使用的是32位系统的.net 4.0,最大线程数是1024个。也就是同时能够处理的任务数量是1024个,当并发的任务超过这个的时候,就会进入队列等待。在前台反馈给用户的就是一个HTTP 503错误。而且新开和保持一个线程都需要消耗硬体资源。
这时,使用async的好处就来了。一个web请求中的一些任务,特别是I/O任务,例如请求查询资料库,线程不会一直处于繁忙状态,会有空闲时间,当线程空闲的时候,会立即进入线程池,被分配到队列中等待的任务。这也就是所谓非同步的意思。
但是如果使用的sync的话,线程就会等待资料库查询返回结果后,再继续往后执行其它逻辑。一旦资料库遇到问题,线程就会一直处于被阻塞等待的状态。而不能进行其他任务的处理。这也是非同步能够提升一定的吞吐量的原因,但实际情况会比较复杂,并不能想答主那样简单粗暴的1000提升到2000那样,具体提升多少需要根据事情情况进行测算。
而且使用async也需要付出一定的代价的——线程的切换是需要消耗一定硬体资源,只是通常硬体的处理能力足够,我们会忽略这种消耗,真的不够用了, 也能够简单的扩容硬体提升。但如果滥用的话,甚至代码中出现了一些逻辑死循环之类的问题,这种消耗就会带来性能反而下降。比如,计算密集型的任务,async基本没有作用,而且会浪费更多资源。
了解了async和sync在线程处理上的区别之后,在实际的编码过程中就好选择了,一些简单的原则——
I/O密集型的任务可以使用async来处理,但是计算密集型的最好使用sync。
逻辑本身就非常简单的,使用sync可以使代码更简洁清晰,而且能减少一些开销。
如果你并不能很好的理解非同步、非阻塞和阻塞的概念,那还是老老实实使用sync,因为大多数项目的并发其实并不能让它产生瓶颈。
非同步定义
关于非同步的定义,网上有很多不同的形式,但是归根结底中心思想是不变的。无论是在http请求调用的层面,还是在cpu内核态和用户态传输数据的层面,非同步这个行为针对的是调用方:
一个可以无需等待被调用方的返回值就让操作继续进行的方法
在多数程序员的概念中一般是指线程处理的层面:
非同步是计算机多线程的非同步处理。与同步处理相对,非同步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程
可以这样通俗的理解,非同步主要解决的问题是不阻塞调用方,调用方这里可以是http请求的发起者,也可以是一个线程。
但此处需要明确的是:非同步与多线程与并行不是同一个概念。
CPU密集型操作
我听有的同学说,非同步解决的是IO密集型的操作,菜菜觉得是不准确的。非同步同样可以解决CPU密集型操作,只不过场景有限而已。有一个前提:利用非同步解决CPU密集型操作要求当前运行环境支持多线程才行,比如javascript这个语言,本质上它的运行环境是单线程的,所以对于CPU密集型操作,javascript会显得力不从心。
非同步解决CPU密集操作一般情况下发生在同进程中,为什么这么说呢,如果发生在不同机器或者不同进程在很多情况下已经属于IO密集型的范围了。这里顺便提醒一下:IO操作可不单单是指磁碟的操作,所有有输入/输出(Input/Output)操作的都可以泛称为IO。
举个栗子吧: 在一个带有UI的软体上点击一个按钮,UI线程会发生操作行为,假如UI线程在执行过程中有一个计算比较耗时的操作(你可以想像成计算1--999999999的和),UI线程在同步操作的情况下会一直等待计算结果,在计算完毕之后才会继续执行剩余操作,在等待的这个过程中,呈现给用户的情况就是UI卡住了,俗称假死了,带给用户的体验是非常不好的。这种情况下,我们可以新启动一个线程去执行这个耗时的操作,当执行完毕,利用某种通知机制来通知原来线程,以便原来线程继续自己的操作。
启动新线程执行CPU密集型操作利用的其实就是多线程的优势,如果是单核CPU,其实这种优势并不明显
IO密集型操作
非同步的优势在IO密集型操作中表现的淋漓尽致,无论是读取一个文件还是发起一个网路请求,菜菜的建议是尽量使用非同步。这里首先普及一个小知识:其实每个外设设备都有自己的处理器,比如磁碟,所以每个外设设备都可以处理自己相应的请求操作。但是处理外设设备信息的速度和cpu的执行速度来比较有著天壤之别。