背景

本系统(支付系统)会在每个月特定时间(如账单日某个时间)接收上游系统发起的大量请求并进行处理,并在处理完成后返回结果给上游系统。而本系统接收到请求进行处理的过程是调用第三方(支付公司)进行处理并获取结果。

系统原实现方案没有采用任何控制请求并发数的措施,接收到上游系统的请求后,就发送给支付渠道进行处理。这样实际上就是来一个请求就启动一个tomcat线程进行处理。

上游系统调用本系统,本系统调用第三方公司同步接口,获得结果后本系统将结果同步返回给上游系统。
假设上游系统的并发数为n,则一开始本系统一秒钟接收n个请求,并将这些请求发往第三方进行处理。假设第三方处理请求需要的时间为t,则对于上游系统来讲,本系统的响应时间比t略大。由于整个调用链上第三方处理时间较长,最短1s,最长可达10s,所以一次完整请求的处理时间是比较长的。本系统处理上游系统请求的TPS = n/t(实际略大于t),由于并发数n不大同时响应时间t较大,所以tps不高。本系统和下游第三方基本没什么压力。

此方案存在的问题:系统接口响应时间依赖于第三方接口响应时间,最长可能长达十几秒。另外就是tps过低。实际上游系统并发量为16,响应时间平均为3s,则tps为5左右,支付系统平均每秒处理5个请求,一小时只能处理18000左右的请求数。

改进方案

对本系统进行改造,主要是把本系统的请求处理接口由同步接口改为异步接口。

即同步接口不再等第三方处理完成之后才返回,而是在本系统接受到请求并进行内部处理,在发送给第三方进行处理之前返回。然后再由异步任务将请求发送给第三方进行处理,将处理结果再通过回调方式或者提供查询接口提供给上游系统。

经此改造后,本系统对上游系统的响应时间由平均3s缩短到30ms。此种改进方案的本质是将上游系统的请求接受过来,然后根据下游的处理能力进行请求分发。那么必须要有一种机制能将请求储存起来,然后根据实际情况将请求取出来发送给下游第三方进行处理。

考虑两种方案,其中一种是消息队列,将请求放到消息队列中慢慢处理,但无法保证消息不丢失。故采用第二种方案:将请求全部入库,然后通过线程池来执行后续任务。

问题及解决办法

1.大量请求没有处理

改进后出现的第一个问题是大量请求在任务表中没有得到处理。原因是:大量请求在一定时间内进入任务表,同时通过线程池将请求取出来执行。由于接受请求的接口响应时间极短(30ms-50ms),tps约为400。线程池核心线程数为40,最大线程数为80,四个服务器实例加起来为320,而下游系统的响应时间平均为3s,tps为100左右。导致每秒约有300个请求得不到处理而进入队列等待,很快四个服务器实例的线程池队列全部放满,之后的请求就无法得到处理了。当上游系统的请求全部接受完毕,主线程停止之后,因任务队列满而得不到处理的请求就在任务表中得不到处理。

解决办法是将任务表中被拒绝的请求用一个状态字段来标记,然后通过补偿任务捞出来再次执行。但是在此过程中又出现了第二个问题。

2.并发更新数据库导致死锁

补偿任务处理逻辑是:从任务表中取出请求记录,当该记录状态为被拒绝,且更新为已执行时更新成功,则将请求发送给第三方进行处理。
这里使用数据库乐观锁来更新状态,防止并发更新和重复执行请求。但是是将状态作为乐观锁标识,更新语句以状态为条件来更新。这种做法带来了死锁问题。原因是:mysql的innoDB行锁是通过给索引项加锁实现的。而索引分为主键索引和非主键索引,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。如果两个线程同时来更新一条记录,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。解决办法是加version字段,更新时以主键id和version作为条件来更新。Version上无索引,更新时只会锁定主键索引,就不会造成死锁。

其实这里也可以使用redis分布式锁,但是需要设置合适的锁过期时间。

后续思考

经过上述改进后,一台服务器的tps为100左右,四台加起来400。为了保证请求不丢失,将所有请求全部入库,此时实际上是将压力全部丢给了数据库,每秒400个并发写入操作(当然,实际上为了减少数据库压力,将上游系统的并发降低,tps也更低一点)。当然,这个并发数还不算高,数据库压力不大。但是,如果并发数进一步增大,则数据库压力将大为增加,此时就不合适采用这种方案了。可能还是需要使用rabbitmq消息队列,通过确认机制来保证消息的可靠性。

线程池处理高并发请求相关推荐

  1. springboot 压测 50并发 线程等待_线程池+CountDownLatch——高并发就是这么简单

    今天和大家分享的是:在开发服务端API时候,如何合理的运用线程池+CountDownLatch来保证API的高并发访问. 首先,作为Java开发的同学来说,java.util.concurrent并发 ...

  2. tomcat使用线程池配置高并发连接

    1:配置executor属性 打开/conf/server.xml文件,在Connector之前配置一个线程池: [html] view plain copy <Executor name=&q ...

  3. 利用libevent 和线程池实现高并发服务器的设计

    主进程添加监听套接字的事件并进行事件循环,将连接描述符放入定义的数据结构中,并在主进程中进行写管道,触发子线程的读管道事件,然后从连接结构中获取连接描述符进行和客户端进行通信.其中主进程和子线程都有不 ...

  4. 解秘 Node.js 单线程实现高并发请求原理,以及串联同步执行并发请求的方案

    最近在做一个支持多进程请求的 Node 服务,要支持多并发请求,而且请求要按先后顺序串联同步执行返回结果. 对,这需求就是这么奇琶,业务场景也是那么奇琶. 需求是完成了,为了对 Node.js 高并发 ...

  5. python线程池模块_python并发编程之进程池,线程池,协程

    需要注意一下 不能无限的开进程,不能无限的开线程 最常用的就是开进程池,开线程池.其中回调函数非常重要 回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去 ...

  6. mysql 保证事物完整性_数据库高并发请求,如何保证数据完整性?详解MySQL/InnoDB的加锁...

    本文是对MySQL/InnoDB中,乐观锁.悲观锁.共享锁.排它锁.行锁.表锁.死锁概念的理解,这些在面试中也经常遇到,如数据库高并发请求,如何保证数据完整性?今天我查阅资料进行了MySQL/Inno ...

  7. python线程池模块_python并发编程之进程池,线程池,协程(Python标准模块--concurrent.futures(并发未来))...

    需要注意一下 不能无限的开进程,不能无限的开线程 最常用的就是开进程池,开线程池.其中回调函数非常重要 回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去 ...

  8. PostgreSQL数据库 OLTP高并发请求性能优化

    PostgreSQL数据库 OLTP高并发请求性能优化   2015-10-14 11:00:00|  作者:德哥:分类: PgSQL PerfTuning| 2015年度PG大象会报名地址: htt ...

  9. Web大规模高并发请求和抢购的解决方案

    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们 ...

最新文章

  1. iMeta期刊顾问James M Tiedje当选中国科学院外籍院士
  2. 全球及中国小型风电产业未来前景展望及发展形势分析报告2021-2027年
  3. 喜欢linux的朋友加QQ群了170838394
  4. ASP.NET Core 应用发布与部署指南
  5. linux之安装Clion和运行使用总结
  6. 【OpenCV函数】轮廓提取;轮廓绘制;轮廓面积;外接矩形
  7. FCKeditor集锦
  8. QT生成在Windows下有图标的exe文件(IDE=QT Creator)
  9. mysql什么是表的并的关系_MySQL表与表的关系
  10. 385.迷你语法分析器
  11. Android开发之Activity(实现Activity跳转)
  12. 针式PKM的设计原则
  13. 纬地道路纵断面设计教程_中铁城际总结公路设计应掌握的知识要领解答
  14. excel数据分组存到一个excel的多个sheet中
  15. 【数据说第四期】篮球比赛中的投篮选择
  16. ssci源刊里有开源期刊吗_2020年SCI期刊影响因子重磅发布!你投过的期刊涨了吗?...
  17. php yar2,[原]PHP-yar拓展源码解读二-protocol篇
  18. 最强脱单指南:如何通过区块链应用快速找到女朋友?
  19. 华为云存储空间图库占比太大_管理云存储空间
  20. 计算机相关专业毕业生参加IT培训是否有必要?

热门文章

  1. 谈谈地图在地震中的应用——以此来纪念“5.12汶川大地震”
  2. Java向下转型的意义
  3. 《高效休息法》书中的精髓:在繁重的工作及生活压力下,我们如何有效地休息,塑造不易疲劳的大脑?
  4. java list初始化数据_Java中初始化List的5种方法 /List的2种去重方式
  5. 每天一个设计模式之 -- 组合模式
  6. (未完成)随机游走模型
  7. 区块链开发初学者编程入门指南
  8. 用JAVA如何实现word文档在线编辑预览的功能?
  9. SYN(用于TCP/IP连接,即建立连接时的第一个包)
  10. NX/UG二次开发—CAM—设置平面铣程序的切削底面(Floor)