高并发的前后端的一般处理

虽然在开发过程中,有些功能可能不需要考虑高并发情况,但是时刻考虑高并发场景处理,是程序员开发过程的一个很好的编程习惯,这种好的习惯也让开发出来的制品比较稳定靠谱 。(本文更多探讨代码层面相关的,比较粗浅,服务架构方面的不涉及,>_<)

高并发的前后端的一般处理

文章插图
【高并发的前后端的一般处理】 
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等 。
  • 响应时间:系统对请求做出响应的时间 。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间 。
  • 吞吐量:单位时间内处理的请求数量 。
  • QPS:每秒响应请求数 。在互联网领域,这个指标和吞吐量区分的没有这么明显 。并发用户数:同时承载正常使用系统功能的用户数量 。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数 。
以下讨论基于的编程语言和使用框架分别为:
php:Laravel框架
JAVA:SpringBoot框架
中间件:redis、Kafka
数据库:MySQL、MongoDB
服务器:linux
服务器集群:k8s
一般对于高并发的处理,按照优先级先后顺序为:
前端 --》 Nginx --》Web应用 --》 服务器 --》数据库
我们就以一个抢购页面为例,来说明一下高并发场景下的一些处理 。
一、前端页面
由于活动页的大部分信息都是固定不变的,所以考虑到网络数据加载优化,一般活动页会生成一个静态页面 。而页面中的抢购按钮会根据活动时间和服务端返回的时间,显示剩余多少抢购时间 。(这里需要使用服务端返回的当前时间来计算剩余多少时间,因为客户端的时间,第一不一定标准,第二有可能客户会人为的调整)
为了防止用户重复点击按钮,也需要控制一下,在用户点击提交按钮之后,按钮禁用 。
二、Nginx
这里主要用到nginx的重写规则,访问某个活动页,通过nginx重写规则,查找是否存在缓存页面,若存在则直接返回,若不存在则进入页面程序 。这个时候需要应用程序里面添加生成缓存页面的机制,这样下次再访问页面时就可以走缓存页面而不用进入程序里面 。(Nginx本身也可以配置限流,但是这里不作为主要考虑对象,它作为性能优越的代理服务器,发挥自身的优势就行了)
三、Web应用
1、缓存
使用缓存,主要还是为了减轻数据库方面的压力,这里有两个原则 。
第一,缓存的信息基本上不会变动,不能将很容易变动的数据缓存起来,那么将失去缓存的意义
第二,缓存数据一致性,比如将活动信息缓存起来,后端将活动信息调整时,同样的也需要修改缓存
第三,缓存的时效性,需要指定缓存的失效时间,比如设置活动结束之后失效
2、CDN加速
3、消息队列(异步)
在抢购过程中,生成订单可能是需要耗费一点时间的,如果让用户等待,可能造成很不好的下单体验 。所以此时会考虑将非必要的业务处理放在异步去处理,比如使用kafka,将处理业务推送一条消息,然后让异步程序去执行生成订单,此时就可以返回一个友好提示给用户,让用户在订单中心查看生成订单结果 。
4、Redis
redis中有个原子性的方法也可以控制并发—setnx,对于业务不是非常复杂的数据请求来说,比如只需要防止客户重复提交,就可以利用setnx来控制 。
//标志是否可以开启事务boolean do_transaction = true;//锁标志,一般以数据ID或者用户ID组合形成唯一标志String redis_index = 'REDIS_'+data_id;//设置锁,后面的值用于后续判断锁是否过期,防止死锁发生Integer result = Redis.setnx(redis_index, time() + 50 );//如果result为1表示设置redis的key成功,可以进行事务提交if (result != 1) { Long previous_transaction_timeout = Redis.get( redis_index ); //如果上次提交事务的超时时间大于当前时间,事务可能还在处理中;反之事务已经超时,造成死锁,需要重新提交事务 if ( previous_transaction_timeout >= time()) {do_transaction = false; } else {Redis.delete( redis_index );result = Redis.setnx( redis_index , time() + 50 );if (result != 1) {do_transaction = false;} }}if ( !$do_transaction ) { return '您的请求太频繁啦~';}//进行事务处理设置成功,返回 1。设置失败,返回 0。即使多个并发同时执行setnx,也只是存在一个能够正确设置并返回1 。为了防止死锁发生,我们可以将redis_val设置成一个过期时间戳,若第一步sentnx没有成功,那么判断redis_val是否已经过期,若过期,则删除当前redis_key,重新调用sentnx 。


推荐阅读