我们生活中经常看到高并发的场景,比如限时优惠券活动、秒杀抢购、12306抢票等,那么这些高并发的情况技术是怎么处理的呢?
假如有1秒钟有1万个请求,我们的apache服务器的连接数500,处理业务的时间为100ms,4台服务器的连接数1秒内能处理500*4/0.1=2万(QPS),完全满足这个要求了,但实际情况是随着请求数的增加,机器处于高负荷的状态,CPU切换次数增大会严重影响处理时长,很有可能由100ms变成500ms甚至更长,此时1秒钟处理的连接数为500*4/0.5=4000,这就会导致大量的请求阻塞.时间越长阻塞越大。根据用户的行为特征,越是阻塞就越会去点击请求这样就会雪上加霜,最终拖垮服务进程
前端采用静态html页面,这样就不需要进行视图渲染了,减少性能开销。一些静态页面、css样式、js可以采用CDN加速。前后端分离可以独立发布,前端有问题,可以直接改好发布而不需要重启服务端,一些大型项目启动服务端会耗费很长时间,如果此时访问量很大的话就会出现阻塞现象。
1)引入缓存
就比如上面的场景,优化代码缩短处理时间也是可以解决的。像这种高并发的情况,能不查数据库就不查数据库,因为数据库的连接也是有限的,比如mysql连接数好像在500左右,而且连接数据库也是耗时间的。这种情况可以想办法将数据放入缓存,比如redis,redis连接数在5万左右且查询速度远比数据库要快,会节省大量查询时间。还有些数据是基础数据一直都不会变的,可以加载到本地缓存比如谷歌的guava
2)尽量使用乐观锁
悲观锁是互斥的,遇到加锁状态必须等待,这会增加阻塞的概率。而且高并发的情况下,有些线程可能永远都拿不到锁,对用户体验是非常差的。
乐观锁可以解决这个问题,不过乐观锁有时候会更新不到数据,此时就会去重试,增加系统开销,但相对悲观锁来说会好很多。可以使用redis的watch功能(redis乐观锁)
3)拆分功能-微服务概念
比如一个系统里有会员信息、商品信息、交易信息,那么我们可以拆成3个微服务,图片、文件用专门的服务器存储,每个应用单独部署,且每个微服务部署集群,每个微服务有自己的数据库。这样就会用户请求跟数据交互都分流了。
这个会增加成本,不过既要性能高又要成本低不太现实,看实际情况了。如果高并发长时间才会搞一次,可以采取灵活部署的办法,平常可以一台服务器部署多个微服务,等活动开始的时候增加机器分开部署
4)削峰
削峰主要通过消息来处理,比如rabbitMq、kafka,这种行为属于偷懒行为,对于实时性要求不高的场景是可以的。一般QPS为1万以上的推荐kafka,实时性要求非常高的用rabbitmq。
5)重启与过载保护
假如服务进程挂了,这样的高并发情况下,重启几乎是无效的,重启就会立马崩溃掉,此时需要加一个服务网关做限流控制,还有用到redis的需要预热操作
1)页面上不让用户重复点击,点击后置灰,别小看这个功能,可以减少50%的无效请求。
2)防作弊,有些用户可以通过技术手段绕过上面的限制,这样就需要在服务端做限制了。比如限流,配置一个用户一定时间内最多访问多少请求,可以用redis存储用户的请求次数,最好用watch功能,避免用户同时操作。有些黄牛可能有多个用户,那么就需要针对IP做限制了,比如增加验证码,当然也可以直接对IP做限流,不过可能会误伤部分用户。
3)有些黄牛会模拟真实场景,比如12306抢票,这样的话只能通过大数据分析了,根据用户行为筛选出哪些账户是僵尸账户,平常不动,高峰期就活跃了,这样可以有针对性的对这些用户做限制操作
高并发的情况下如果不做控制很容易出现数据错乱的问题,解决这个问题需要数据同步。
大致思路有:
1、悲观锁
比如synchronized,lock,redis分布式锁,悲观锁容易造成阻塞
2、同步队列
请求全部进入一个同步队列里,然后一个个操作,跟悲观锁类似,也容易造成阻塞
3、乐观锁
数据库加版本号,每次更新操作需要带版本号进行幂等操作。也可以用redis的watch功能,乐观锁操作会有很多无效操作,增加重试开销,但相对引起阻塞的问题这点性能开销还是可以接受的,所以高并发情况下最好还是用乐观锁
互联网正在高速发展,使用互联网服务的用户越多,高并发的场景也变得越来越多。电商秒杀和抢购,是两个比较典型的互联网高并发场景。虽然我们解决问题的具体技术方案可能千差万别,但是遇到的挑战却是相似的,因此解决问题的思路也异曲同工。
个人整理并发解决方案。
a.应用层面:读写分离、缓存、队列、集群、令牌、系统拆分、隔离、系统升级(可水平扩容方向)。
b.时间换空间:降低单次请求时间,这样在单位时间内系统并发就会提升。
c.空间换时间:拉长整体处理业务时间,换取后台系统容量空间。