虽然在开发过程中,有些功能可能不需要考虑高并发情况,但是时刻考虑高并发场景处理,是程序员开发过程的一个很好的编程习惯,这种好的习惯也让开发出来的制品比较稳定靠谱。(本文更多探讨代码层面相关的,比较粗浅,服务架构方面的不涉及,>_<)
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
以下讨论基于的编程语言和使用框架分别为:
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。
四、服务器
这里主要用到负载均衡和限流,系统对于访问的数据列承载和处理能力是有限的,所以需要通过限流和负载均衡,将请求分一分,达到最大的优化程序。比如某个节点一次性只能处理500个并发,但是实际场景里面可能会有上万的并发请求,如果不进行处理,系统直接就崩溃了。
对于微服务架构来说,会将服务转移到不同的微服务上,比如订单微服务、会员微服务、促销微服务、商品库存微服务等等。微服务架构需要考虑微服务治理。
五、数据库
请求已经落到数据库这里的时候,说明前面各个节点该优化的都已经优化完了,只能用数据库硬钢了。数据库的一般优化和处理为:
1、水平分割,比如存储日志表,可以按照日期后缀去区分保存,例如log_20200101
2、根据实际用到的查询条件,查看数据库索引是否建立,然后已经建立的索引是否合理
3、数据冗余,有些时候必须得连表才能获取到额外的信息,我们可以考虑适当的增加一些冗余字段
补充:数据库数据方面,需要考虑是否有些数据可以进行清理或者转移备份