【后台技术】异步编程指北,问题和重点

news2024/11/19 16:25:58

导语:同步、异步,并发、并行、串行,这些名词在我们的开发中会经常遇到,这里对异步编程做一个详细的归纳总结,希望可以对这方面的开发有一些帮助。

内容大纲:

1、几个名词的概念

多任务的时候,才会遇到的情况,如:同步、异步,并发、并行。

1.1 理清它们的基本概念

并发:多个任务在同一个时间段内同时执行,如果是单核心计算机,CPU会不断地切换任务来完成并发操作。

并行:多任务在同一个时刻同时执行,计算机需要有多核心,每个核心独立执行一个任务,多个任务同时执行,不需要切换。

同步: 多任务开始执行,任务A、B、C全部执行完成后才算是结束。

异步: 多任务开始执行,只需要主任务A执行完成就算结束,主任务执行的时候,可以同时执行异步任务B、C,主任务A可以不需要等待异步任务B、C的结果。

并发、并行,是逻辑结构的设计模式。

同步、异步,是逻辑调用方式。

串行是同步的一种实现,就是没有并发,所有任务一个一个执行完成。

并发、并行是异步的2种实现方式。

1.2 举一个例子

你的朋友在广州,但是有2辆小汽车在深圳,需要你帮忙把这2辆小汽车送到广州去。

同步的方式,你先开一辆小汽车到广州,然后再坐火车回深圳,再开另外一辆小汽车去广州。这是串行的方法,2辆车需要的时间也就更长了。

异步的方式,你开一辆小汽车从深圳去广州,同时请一个代驾把另外一辆小汽车从深圳开去广州。这也就是并行方法,两个人两辆车,可以同时行驶,速度很快。

并发的方式,你一个人,先开一辆车走500米,停车跑回来,再开另外一辆车前行1000米,停车再跑回来,循环从深圳往广州开。并发的方式,你可以把2辆车一块送到朋友手里,但是过程还是很辛苦的。

1.3 思考问题

你找一家汽车托运公司,把2辆车一起托运到广州。这种方式是同步、异步,并发、并行的哪种情况呢?

2、并发/并行执行会遇到的问题

2.1 问题1:并发的任务数量控制

假设:某个接口的并发请求会达到1万的qps,所以对接口的性能、响应时长都要求很高。

接口内部又有大量redis、mysql数据读写,程序中还有很多处理逻辑。如果接口内的所有逻辑处理、数据调用都是串行化,那么单个请求耗时可能会超过100ms,为了性能优化,就会把数据读取的部分与逻辑计算的部分分开来考虑和实现,能够独立的部分单独剥离出来作为异步任务来执行,这样就把串行化的耗时优化为并发执行,充分利用多核计算机的性能,减少单个接口请求的耗时。

 资料领取直通车:大厂面试题锦集+视频教程

Linux服务器学习网站:C/C++Linux服务器开发/后台架构师

假设的数据具体化,如:这个接口的数据全部是可以独立获取(支持并发),需要读取来自不同数据结构的redis共10个,读取不同数据表的数据共10个。那么一次请求,数据获取就会启动10个redis读取任务,10个mysql读取任务。每秒钟1万接口请求,会有10万个redis读取任务和10万个mysql读取任务。这21万的并发任务,在一秒钟内由16/32核的后端部署单机来完成,虽然在同一时刻的任务数量不一定会是21万(速度快的话会少于21万,如果处理速度慢,出现请求积压拥堵,会超过21万)。

这时候,会遇到的瓶颈。

内存,如果每个任务需要500k内存,那么 210k*0.5M=210*0.5G=105G.

CPU,任务调度,像golang的协程可能开销还小一些,如果是java的线程调度,操作系统会因为调度而空转。

网络,每次数据读取5k,那么200k*5k=200*5M=1G.

端口,端口号最多能分配出来65536个,明显不够用了。

数据源,redis可以支持10万qps的请求,但是mysql就难以支持10万qps了。

上面可能出现的瓶颈中,通过计算机资源扩容可以解决大部分问题,比如:部署50个后端实例,每个实例只需要应对200的qps,压力就小了很多。对于数据源,mysql可以有多个slave来支持只读的请求。

但是,如果接口的并发量更大呢?或者某个/某些数据源读取出现异常,需要重试,或者出现拥堵,接口响应变慢,任务数量也就会出现暴增,后端服务的各方面瓶颈又会随之出现。

所以,我们需要特别注意和关心后端开启的异步任务数量,要做好异常情况的防范,及时中断掉拥堵/超时的任务,避免任务暴增导致整个服务不可用

2.2 思考问题

你要如何应对这类并发任务暴增的情况呢?如何提前预防?如何及时干预呢?

2.3 问题2:共享数据的读写顺序和依赖关系

共享数据的并发读写,是并发编程中的老大难问题,如:读写脏数据,旧数据覆盖新数据等等。

而数据的依赖关系,也就决定了任务的执行先后顺序。

为了避免共享数据的竞争读写,为了保证任务的先后关系,就需要用到锁、队列等手段,这时候,并发的过程又被部分的拉平为串行化执行。

2.4 举个例子

https://www.ticketmaster.com/eastern-conf-semis-tbd-at-boston-boston-massachusetts/event/01005C6AA5531A90

NBA季后赛,去现场看球,要抢购球票,体育馆最多容纳1万人(1万张球票)。

体育馆不同距离、不同位置的票,价格和优惠都不相同。有单人位、有双人位,也有3、4人位。你约着朋友共10个人去看球,要买票,要选位置。这时候抢票就会很尴尬,因为位置连着的可能会被别人抢走,同时买的票越多,与人冲突的概率就越大,会导致抢票特别困难。

同时,这个系统的开发也很头大,抢购(秒杀)的并发非常大,预计在开始的一秒钟会超过10万人同时进来,再加上刷票的机器人,接口请求量可能瞬间达到100万的QPS。

较简单的实现方式,所有的请求都异步执行,订单全部进入消息队列,下单马上响应处理中,请等待。然后,后端程序再从消息队列中串行化处理每一个订单,把出现冲突的订单直接报错,这样,估计1秒钟可以处理1000个订单,10秒钟可以处理1万个订单。考虑订单的冲突问题,1万张球票的9000张可能在30秒内卖出去,此时只处理了3万个订单,第一秒钟进来的100万订单已经在消息队列中堆积,又有30秒钟的新订单进来,需要很久才可以把剩下的1000张球票卖出去啊。同理,下单的用户需要等待太久才知道自己的订单结果,这个过程轮询的请求也会很多很多。

 资料领取直通车:大厂面试题锦集+视频教程

Linux服务器学习网站:C/C++Linux服务器开发/后台架构师

换一种方案,不使用队列串行化处理订单,直接并发的处理每一个订单。那么处理流程中的数据都需要梳理清楚。

1 针对每一个用户的请求加锁,避免同一个用户的重入;

2 每一个/组座位预生成一个key:0,默认0说明没有下单;

3 预估平均每一个订单包含2个/组座位,需要更新2个座位key;

4 下单的时候给座位key执行 INCR key 数字递增操作,只有返回1的订单才是成功,其他都是失败;

5 如果同一个订单中的座位key有冲突的情况下,需要回滚成功key(INCR key = 1)重置(SET key 0);

6 订单成功/失败,处理完成后,去掉用户的请求锁;

7 订单数据入库到mysql(消息队列,避免mysql成为瓶颈);

综上,需要用到1个锁(2次操作),平均2个座位key(每个座位号1-2次操作),这里只有2个座位key可以并发更新。为了让redis不成为数据读写的瓶颈(超过100w的QPS写操作),不能使用单实例模式,而要使用redis集群,使用由10-20个redis实例组成的集群,来支持这么高的redis数据读写。

算上redis数据读写、参数、异常、逻辑处理,一个请求大概耗时10ms左右,单核至少可以支持100并发,由于这里有大量IO处理,后端服务可以支持的并发可以更高些,预计单核200并发,16核就可以支持3200并发。总共需要支持100万并发,预计需要312台后端服务器。

这种方案比队列的方案需要的服务器资源更多,但是用户的等待时间很短,体验就好很多。

2.5 思考问题

实际情况会是怎样呢?会有10万人同时抢票吗?会有100万的超高并发吗?订票系统真的会准备300多台服务器来应对抢票吗?

3、状态处理:忽略结果

3.1 使用场景和案例

使用场景,主流程之外的异步任务,可能重要程度不高,或者处理的复杂度太高,有时候会忽略异步任务的处理结果。

案例1:异步的数据上报、数据存储/计算/统计/分析。

案例2:模板化创建服务,有很多个任务,有前后关联任务,也有相互独立任务,有些执行速度很慢,有些任务失败后也可以手动重试来修复。

忽略结果的情况,就会遇到下面的问题。

3.2 问题1:数据一致性

看下案例1的情况。

异步的日志上报,是否成功发送到服务端呢?

异步的指标数据上报,是否正确汇总统计和发送到服务端呢?

异步的任务,数据发送到消息队列,是否被后端应用程序消费呢?

服务端是否正常存储和处理完成呢?

如果因为网络原因,因为并发量太大导致服务负载问题,因为程序bug的原因,导致数据没能正确上报和处理,这时候的数据不一致、丢失的问题,就会难以及时排查和事后补发。

如果在本地完整记录一份数据,以备数据审查,又要考虑高并发高性能的瓶颈,毕竟本地日志读写性能受到磁盘速度的影响,性能会很差。

3.3 问题2:功能可靠性

看下案例2的情况。

创建服务的过程中,有创建代码仓库、开启日志采集和自定义镜像中心,CI/CD等耗时很长的任务。这里开启日志采集和自定义镜像中心如果出现异常,对整个服务的运行没有影响,而且开发者发现问题后也可以自己手动操作下,再次开启日志采集和自定义镜像功能。所以在模板化处理中,这些异步处理任务就没有关注任务的状态。

那么问题就很明显,模板化创建服务的过程中,是不能保证全部功能都正常执行完成的,会有部分功能可能有异常,而且也没有提示和后续指引。

当然模板化创建服务的程序,也可以把全部任务的状态都检查结果,只是会增加一些处理的复杂度和难度。

3.4 思考问题

实际开发中,有遇到类似上面的两个案例吗?你会如何处理呢?所有的异步任务,都会检查状态结果吗?为什么呢?

4、状态处理:结果返回

4.1 使用场景和案例

大部分的异步任务对于状态结果还是很关注的,比如:后续的处理逻辑或者任务依赖某个异步任务,或者异步任务非常重要,需要把结果返回给请求方。

案例1:模板化创建服务的过程中,需要异步创建服务的git代码仓库,还要给仓库添加成员、webhook、初始化代码等。整个过程全部串行化作为一个任务的话,耗时会比较长。可以把创建服务的git代码仓库作为一个异步任务,然后得到成功的结果后再异步的发起添加成员、加webhook、初始化代码等任务。同时,这里的CI/CD有配置相关,有执行相关,整个过程也很长,CD部署成功之后才可以开启日志采集等配置,所以也需要关注CD部署的结果。

案例2:各种webhook、callback接口和方法,就是基于回调的方式,如:golang中的channel通知,工蜂中的代码push等webhook,监控告警中的callback等。

案例3:发布订阅模式,如引入消息队列服务,主程序把数据发送给消息队列,异步任务订阅相应的主题然后处理。处理完成后也可以把结果再发送给消息队列,或者把结果发送给主调程序的接口,或者等待主调程序来查询结果,当然也可能是上面的忽略结果的情况。

从上可以总结出来,对于异步任务的状态处理,需要关注结果的话,有两种主要的方法,分别是:轮询查询和等待回调。

4.2 方法1:轮询查询

上面的案例1中,模板化创建服务的过程很慢,所以整个功能都是异步的,用户大概要等待10s左右才知道最后的结果。所以,用户在创建服务之后,浏览器会不断轮询服务端接口,看看创建服务的结果,各个步骤的处理结果,服务配置是否都成功完成了。

类似的功能实现应该有很多,比如:服务构建、部署、创建镜像仓库、抢购买票等,把任务执行和任务结果通过异步的方式强制分离开,用户可以等待,但是不用停留在当前任务中持续等待,而是可以去做别的事情,随时回来关注下这个任务的处理结果就好了。大部分执行时间很长的任务都会放到异步线程中执行,用户关注结果的话,就可以通过查询的方式来获取结果,程序自动来返回结果的话,就可以用到轮询查询了。

局限性1:频率和实时性

轮询的方式延时可能会比较高,因为跟定时器的间隔时间有关系。

局限性2:增加请求压力

因为轮询,要不断地请求服务端,所以对后端的请求压力也会比较大。

4.3 方法2:通知回调

等待回调几乎是实时的,处理有结果返回就马上通过回调通知到主程序/用户,那么效率和体验上就会好很多。

但是这里也有一个前提要求,回调的时候,主程序必须还在运行,否则回调也就没有了主体,也就无效了。所以要求主程序需要持续等待异步任务的回调,不能过早的退出。

一般程序中使用异步任务,需要得到任务状态的结果,使用等待回调的情况更多一些。

特别注意1:等待超时

等待的时间,一般不能是无限长,这样容易造成某些异常情况下的任务爆炸,内存泄露。所以需要对异步任务设置一个等待超时,过期后就要中断任务了,也就不能通过回调来得到结果了,直接认为是任务异常了。

特别注意2:异常情况

当主程序在等待异步任务的回调时,如果异步任务自身有异常,无法成功执行,也无法完成回调的操作,那么主程序也就无法得到想要的结果,也不知道任务状态的结果是成功还是失败,这时候也就会遇到上面等待超时的情况了。

特别注意3:回调地狱

使用nodejs异步编程的时候,所有的io操作都是异步回调,于是就很容易陷入N层的回调,代码就会变得异常丑陋和难以维护。于是就出现了很多的异步编程框架/模式,像:Promise,Generator,async/await等。这里不做过多讲解。

4.4 思考问题

实际工作中,还有哪些地方需要处理异步任务的状态结果返回呢?除了轮询和回调,还有其他的方法吗?

5、异常处理

同步的程序,处理异常情况,在java中只需要一个 try catch 就可以捕获到全部的异常。

5.1 重点1:分别做异常处理

异步的程序,try catch 只能捕获到当前主程序的异常,主程序中的异步线程是无法被捕获的。这时候,就需要针对异步线程中的异步任务也要单独进行 try catch 捕获异常。

在golang中,开启协程,还是需要在异步任务的defer方法中,加入一个 recover() ,以避免没有处理的异常导致整个进程的panic。

5.2 重点2:异常结果的记录,查询或者回调

当我们把异步任务中的异常情况都处理好了,不会导致异步线程把整个进程整奔溃了,那么还有问题,怎么把异常的结果返回给主进程。这就涉及到上面的状态处理了。

如果可以忽略结果,那么只需要写一下错误日志就好了。

如果需要处理状态,那就要记录下异常信息或者通知回调给到主进程。

5.3 思考问题

实际工作中,你会对所有的可能异常情况都做相应的处理吗?异常结果,都是怎么处理的呢?

6、典型场景和思考

前面已经讲到一些案例,总结下来的典型场景有如下几种

6.1 订阅发布模式,消息队列

6.2 慢请求,耗时长的任务

6.3 高并发、高性能要求时的多任务处理

6.4 不确定执行的时间点,触发器

人脑(单核)不擅长异步思考,电脑(多核)却更适合。

编程的时候,是人脑适配电脑,还是电脑服务人脑?

在大部分的编程中,大家都只需要考虑同步的方式来写代码逻辑。少部分时候,就要考虑使用异步的方式。而且,有很多的开发框架、类库已经把异步处理封装,可以简化异步任务的开发和调试工作。

所以,对于开发者来说,默认还是同步方式思考和开发,当不得不使用异步的时候,才会考虑异步的方式。毕竟让人脑适配电脑,这个过程还是有些困难的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jmeter压力测试报告

出版社智能智造测试报告 (二期版本) 2022年11月 目 录 1. 测试背景 1.1. 项目背景 1.2. 测试目的 1.3. 测试时间 1.4. 测试资源 1.5. 参考资料 2. 测试范围 3. 性能需求指标 3.1. 业界指标 4. 测试工具 5. 测试环境 5.1. 阿里云测试环境软…

搭建Gitlab

Gitlab是目前被广泛使用的基于git的开源代码管理平台, 基于Ruby on Rails构建, 主要针对软件开发过程中产生的代码和文档进行管理 一、搭建gitlab服务器,统一管理软件项目 第一步: 创建一个4G内存的虚拟机,否则很容易启动不了,报…

(附源码)计算机毕业设计Java“华商转转”平台的设计和实现

项目运行 环境配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: Springboot mybatis Maven Vue 等等组成,B/…

python常用进制转换

整数之间转换 # 1. 10 -> 16 hex(number)# 2. 10 -> 2 bin(number)# 3. 10 -> 8 oct(number)# 4. x进制 -> 10 int(Union[str, bytes, bytearray],basex) ------------------ print(int("0x16", base16)) // 22字符串转整数 # 10进制 val int(10) pri…

SPP-学习笔记

Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition SPP提出的原因 1、现有的深度卷积神经网络(spp出现之前的)需要固定大小的输入图像(例如224224)。往往需要对图片裁剪或者resize,导致图片信息损失或者产生几何畸变。这样可能会损…

奥比中光亮相全球1024开发者节,与科大讯飞达成战略合作

作者 | 奥比中光 编辑 | 3D视觉开发者社区 11月17日-23日,第五届世界声博会暨2022科大讯飞全球1024开发者节在安徽合肥举办,奥比中光作为3D视觉感知头部企业参展,并与科大讯飞达成战略合作,共同赋能3D视觉行业应用开发。 本次参…

如何利用现代工具来管理多项目

多项目管理是如今现代企业管理时常常遇到的一个难题。不同于单项目管理,多个项目同时进行管理要复杂得很多。而单纯的手工管理方式已经满足不了多管理的复杂需求,项目负责人想要保障在预定的时间内,又快又好地完成整体项目,便需要…

工厂模式解耦-交由spring来完成

上面两个小节一直在谈论解耦,从入门的多例到升级的单例BeanFactory工厂类是我们自己手工写的。 BeanFactory主要做了3件事: 1.读取配置文件(可以是properties或xml类型的文件,示例中用的是properties文件) 2.获取类…

OC RSA加密解密

好久好久没有更新了。。。你们等的急不急。。这不,我就姗姗来迟了。。。本文重点讲解一下iOS系统下的RSA加密解密问题。 一般为了安全,私钥是不会给前端暴露出来 的,只会通过私钥生成一个公开的公钥提供给外部对数据进行加密。将加密后的数据…

残差网络ResNet解读

一、残差网络的定义 残差网络的核心是解决增加深度带来的退化问题,这样能够通过单纯增加网络深度来提高网络性能。 残差单元以短连接的形式,将单元的输入直接与单元输出加在一起,然后再进行激活。 Weight为抽取特征的网络层 Addition时xl和…

RK3568平台开发系列讲解(视频篇)摄像头采集视频的相关配置

🚀返回专栏总目录 文章目录 一、权限配置二、配置摄像头2.1、打开摄像头2.2、预览格式2.3、预览尺寸沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Android 平台的摄像头的采集核心部分都是在 Native 层构建的,所以这就会涉及 JNI 层的一些转换操作。 一、权限配置…

Linux | 进程间通信 | 匿名管道 | 命名管道 | 模拟代码实现进程通信 |

文章目录进程通信的意义匿名管道通信原理管道的访问控制进程控制管道的特点命名管道进程通信的意义 之前聊进程时,讲过一个性质,即进程具有独立性,两个进程之间的交互频率是比较少的。就连父子进程也只是共享代码,修改父子进程中…

MODBUS通信系列之数据处理

MODBUS通信专栏有详细文章讲解,这里不再赘述,大家可以自行查看。链接如下: SMART S7-200PLC MODBUS通信_RXXW_Dor的博客-CSDN博客_smart200做modbus通讯MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。自…

化工机械基础期末复习题及答案

化工设备机械基础复习题 一 选择题 1、材料的刚度条件是指构件抵抗( B )的能力。 A.破坏 B.变形 C.稳定性 D.韧性 2、一梁截面上剪力左上右下,弯矩左顺右逆,描述正确的是&#xff08…

上班总结测试报告

出版社智能智造 测试报告 项目名称 出版社智能智造 测试版本 二期版本20221103 级别 用户使用 编写人 罗胜杰 日期 2022.11.15 目 录 1. 测试概述 1.1. 编写目的 1.2. 产品需求介绍 1.3. 参考资料 2. 测试计划执行情况 2.1. 测试范围及策略 2.2. 本…

[附源码]SSM计算机毕业设计基于的花店后台管理系统JAVA

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

【Python百日进阶-WEB开发-冲進Flask】Day181 - Flask简单流程

文章目录一、day01项目环境和结构搭建1.1 新建虚拟环境1.2 安装Flask1.3 配置Python解释器二、后端知识要点2.1 Flask 文档2.2 实例化flask对象2.2.1 新建独立的配置文件settings.py2.2.2 实例化flask对象时加载配置文件2.3 基本路由2.3.1 常用路由及唯一性2.3.2 路由底层调用2…

中央空调系统运行原理以及相关设备介绍

目录前言一、中央空调系统工作原理1-1、工作原理1-2、中央空调系统构成二、室内空调三、制冷机组3-1、概述3-2、原理3-3、蒸发器3-4、冷凝器3-5、压缩机3-6、总结四、冷却塔总结前言 今天也是为了30岁开始养老而奋斗的一天。 一、中央空调系统工作原理 1-1、工作原理 中央空…

FFmpeg入门 - rtmp推流

FFmpeg入门 - 视频播放_音视频开发老马的博客-CSDN博客介绍了怎样用ffmpeg去播放视频. 里面用于打开视频流的avformat_open_input函数除了打开本地视频之外,实际上也能打开rtmp协议的远程视频,实现拉流: ./demo -p 本地视频路径 ​ ./demo -p rtmp://服务器ip/视频流路径 这篇…

JVM垃圾回收总结

常见面试题 如何判断对象是否死亡 简单介绍一下强引用、软引用、弱引用、虚引用 如何判断常量是一个废弃常量 如何判断类是一个无用类 垃圾收集有哪些算法、各自的特点? 常见的垃圾回收器有哪些? 介绍一下CMS,G1收集器? minor gc和…