高并发分布式计算-生产实践

简介

记录一个项目的技术实现,主要谈这个项目的大请求的高并发处理这一块。这个项目最终通过多种技术组合,达到削峰填谷地秒级分布式计算大请求的能力,且各服务/接口间的熔断和自动恢复避免了某1个挂起的服务使得其他服务挂起的单点故障。这个项目申请了多份专利

背景

其实项目整体的技术选型本身是一次很有意思的经历,里面有太多的故事(技术访谈、技术调研、多套方案的POC、小组讨论、确认技术选型、异地出差做项目调研、跨部门确认技术选型、向各技术部门领导通报技术可行性研究、协助兄弟部门技术提升等),最有意思的是兄弟部门架构师(已离职)的一席话:这个项目已经好多波人找我调研了,其实有方案一直不能落地,希望你们能搞定!当时,我的头顶有几只乌鸦飞过……。几百人IT团队的架构师都推不动,我们能量有那么大吗。最后生产实践证明:相比之下,我们的方案更加轻快、更加流程化、更加既见既所得、且一处配置处处一致

为了控制文章的篇幅,此文只说明大请求高并发的技术实现方案

业务场景

  1. 在1次业务请求里:需要处理1~10+个对象。这1~10+个对象在被处理时,会共使用同1张300~700K的图片
  2. 请求里的1个对象被处理的步骤:
    1)获取对应的Html模板,每分模板大概70K左右
    2)Html模板的velocity引擎参数替换
    3)Html转PDF
    4)向第三方上传PDF和图片,作电子签名
    5)将签名的PDF上传到云存储平台,得到fileid
    6)将签名的fileid + 由业务同事配置的印章信息,调用第三方接口,作电子印章
  3. 业务同事会在页面上新增、修改Html模板。换言之,模板不能是项目的资源文件

技术说明

  1. 如果需要使用MQ,需要使用集团基于RocketMq封装的MQ,消息体最大支持128K
  2. 业务高峰时300+tps。后面测试同事反馈:压测时他们更狠,用了1w+的瞬时并发压测4台机器,依然ok
  3. 1次业务请求需在5秒内处理完毕并返回结果。生产数据表明:1个对象被处理的总时间基本落在300毫秒~1.5秒之间,而由于是分布式计算,因此1次请求的总耗时≈1个对象的总耗时(木桶效应:请求中最耗时的那1个对象)
  4. 当时这个项目所属的系统没有跑在微服务,而是跑在云主机上
    换言之,这个系统的集群会run其他的业务功能
    再换言之,不能因为这个高并发的项目让集群里的机器瘫痪,影响到其它同样重要的业务逻辑的运行

方案

服务 = 请求持久化 + 分布式计算 + MQ异步通知

实现

请求持久化

示意图

说明

  • 流程说明(为避免并发因素,下文的执行顺序不能调换):
  1. 对请求A里的对象A、B做必要性的参数检查。检验失败立马返回错误信息
  2. 在分布式环境下生成唯一的请求编号reqId。参见之前写的一篇文章: 分布式UUID的生成
  3. 将当前请求信息(主要是reqId、请求的初始处理状态)写入Oracle
  4. 在Redis中记录基于reqId的分布式锁,控制后面多实例间的并发。hset-->reqId(key):1(value)
  5. 分别给对象A、B定义3个核心属性:
    1)当前对象所属请求的reqId
    2)对象编号。生成编号的方法和生成reqId的一致
    3)对象持久化的时间(当前时间)
  6. 通过上一步,得到reqId和所有对象编号的对应关系,将对应关系持久化到Redis中。hset-->reqId(key):List(value)
  7. 将对象A、B(含图片)持久化到Redis队列中,或则数据库中。说明:
    • 配置Redis的Queue深度为2000(可动态调整)
    • 若发现队列满了,则将当前对象持久化到Oracle中作二级缓存,并在Redis中累加二级缓存的对象数: redis.incrby("SL_CACHE",1);
    • 若Redis记录的二级缓存对象数大于0:后续请求的对象直接写Oracle,确保Redis队列和Oracle二级缓存队列保持顺序的一致性
    • 若发现队列未满且二级缓存对象数不大于0,则将当前对象持久化到Redis的Queue中
  8. 返回调用方reqId
  • 其他说明:

    Redis读的速度是11万次/s,写的速度是8.1万次/s。每1次写的速度小于0.0125毫秒,如果把应用层和Redis之间的网络消耗算在内,最大应该是1个毫秒级。如果1次请求有10个对象的持久化,最大也就是10个毫秒左右。加上Oracle的1次请求(不是请求里的对象)的持久化时间,整个持久化时间大概20~50ms左右,调用方可以快速得到reqId

分布式计算、MQ通知

示意图

说明

  • 流程说明:
  1. 集群里的每台实例开启4个无限循环的线程。下面的步骤都是在1个线程中执行
  2. 当前线程判断是否需要将二级缓存的对象补充到Redis队列(需控制并发,避免重复载入二级缓存)。判断依据:
    1) 队列深度小于50%
    2) 二级缓存对象数大于0
  3. 从Redis队列中获取的对象若为空,睡300毫秒后,再继续从第2步开始
  4. 和当前时间比较,若对象持久化的时间已经超过120秒了,则立马跳到第2步开始
  5. 处理对象(执行业务逻辑),这一步涉及熔断。在标准web应用中通过AOP实现了一版,切换到微服务后,利用Hystrix组件实现了熔断。这两版虽然是不同的实现,但是控制逻辑一致,示意图中对熔断逻辑做了说明
  6. 将当前对象的处理结果(Map),回写到Redis中。对象编号(key):处理结果(value)
  7. 通过当前对象绑定的reqId,从Redis中取得reqId和所有对象编号的对应关系,在Redis中查询每个对象编号处理结果(Map)。若某个对象编号没有处理结果,表示该对象处理中或则没有处理,则跳到第2步开始
  8. 当前请求的所有对象处理完成后,就可以将所有对象的处理结果(Map)封装成MQ,异步通知调用方。同1个请求的不同对象,可能会被不同实例的线程处理,因此只能让一个线程获得上文提到的基于reqId的分布式锁来发送MQ消息,方法:若某1个线程执行redis.del(reqId)的返回值大于0,则获取到分布式锁

FAQ

汇总一下之前小伙伴的疑问:

  1. 分别对1次业务请求的n个对象,发起n次请求?

    不行。1次请求拆成n次Http请求调用,每次调用又带着300~700K的图片的话,在高并发下,千兆网卡可能吃不消(何况很多实例都是虚拟化的,多台实例共用同1台物理机器,多台实例共用同1张网卡,更会加重这种影响)。我的猜想没有错:有一次UIOC事件(其他兄弟的另1个项目)就是因为并发过高,网卡挂起,网卡间断性的发网络请求

  2. 用MQ的方式通知集群处理业务请求?

    不行。 集团的1条MQ消息,容量最大是128K,而请求中的图片就有300~600K

  3. 同步接口来处理业务请求?

    不行。1个对象的处理时间若是700ms,1个请求的10个对象的处理时间是7秒,超出了1个请求2秒得到结果的要求

  4. 同步接口 + ExecutorService线程池并行处理业务请求里的各个对象?

    不行。主要是两个方面:
    1)由于涉及PDF流的运算,会消耗大量内存,为了不影响其他的业务逻辑的运行,必须限定线程池的个数和队列深度。若不限定,那么大的图片存储在内存中很快会OOM
    2)在高并发下,线程数和队列深度一旦限定,有些请求因达到线程池的限制会被直接拒绝。同时,同1个请求10+左右的对象,可能会分m批运行(m=10/线程数),若每批运行时间是700ms,会有超过整个请求的处理时间在2秒内的限制的可能

  5. 这个项目为什么一定要用异步接口呢?

    作为服务方,尽量不成为公司整个业务流程的性能瓶颈节点。 调用方与服务方的网络连接越快结束越好,避免服务方运算速度变慢(比如YGC、OGC、FGC、系统中其他耗性能的功能running中等),而导致调用方HTTP连接的等待,如果此时调用方没有配置超时时间,会发生灾难性的贯穿性的雪崩效应。同时鉴于单个对象的处理链条长,PDF流运算较耗性能,如果几千个对象同时同步的在服务方处理,服务方必然会资源耗尽。所以这个项目需要通过异步队列的形式,削峰填谷

  6. 不考虑用MongoDB来替代Oracle的写操作?

    有考虑过,不过这个项目所属的系统没有MongoDB,需要预算审批。如果替代过后的好处:
    1)减轻Oracle的写压力
    2)由于磁盘以bson存储,易于扩展监控类字段
    3)通过MongoDB优秀的写性能,可以减少单个对象的处理时间
    由于1次请求整体响应时间在1秒左右,已经达到5秒内的要求,性能过得去,所以没有申请这笔预算

  7. 为什么不直接将对象持久化到Redis中,而要用数据库做二级缓存呢?

    主要是担心海量并发时,Redis的队列深度不控制,会将Redis撑爆。毕竟为了读写效率,每个对象都会存储300~700K的图片。如果1个请求有10个对象,则会存7M,48G的Redis总共存储7021个请求,若按照300tps且来不及消费Redis队列的话,最快23秒就会将Redis爆掉。因此,队列深度是2000的话,最大占用Redis容量是1.3G=2000*700/1024/1024

  8. 为什么每台实例只开启4个线程?

    当时测试环境压测的主要的环境指标:申请和生产环境同样的CPU和内存,每次压测的处理对象一样。线程数从1配置到10,主要考核2个核心指标:每秒处理的对象数、每个对象处理的耗时。不难看出这2个指标是互为倒数关系,数学上分别是直线和双曲线,他们一定在第一象限有个交点,这个交点就是4。4个线程时,这两个指标是最好的。其实也不难理解,在内存足够的情况下,影响线程性能的就是CPU,而服务器的CPU是四核的,每个核处理1个用户线程是最佳的

  9. 为什么Redis队列里的对象超过120秒后丢弃?二级缓存只取60秒内的数据?

    1)文中提到的各种阈值,它们之间是有联系的,是通过计算得到的。比如:这里的60、120:队列深度(2000)低于50%时,取1000个对象补充;而每个对象120秒就会被丢弃,所以只用取60秒内的数据;当然这也和集群中的实例数有关。因每家公司集群环境不一样,阈值配置的具体值不深入讨论
    2) 对象之所以要有过期时间而被丢弃,主要是为了当数据积压发生时(队列生产者的生产的速度比消费者消费的速度快),避免新的请求迟迟得不到消费
    3) 生产事件:截止到2019.06,这种情况生产环境一共出现3次,第1次出现这个情况时没有这个特性,运维同事又迟迟没有Redis的操作权限,也不敢flush数据导致加重了业务影响----集群一直消费很早之前(半小时、1个小时之前)的请求。其实这个超时丢弃的特性早已报备并计划上线,只不过计划赶不上变化。第2、3次出现这个情况时,因为有了这个特性,集群进行了自我修复,基本无感知。至于这3次情况产生的根源性原因各不相同,都不是因我们的服务自身而起,虽不是我们的锅,但毕竟我们可以做到更好,减轻别人黑锅对我们的影响,你说呢?

  10. 丢弃的对象后面会补偿处理吗?

    不会,业务上不需要。调用我们服务的属于App交互类的请求,接口上保持幂等性即可

原文地址:https://www.cnblogs.com/jacobus/p/11087250.html

时间: 2024-10-07 17:17:01

高并发分布式计算-生产实践的相关文章

构建高并发高可用的电商平台架构实践

从各个角度总结了电商平台中的架构实践,由于时间仓促,定了个初稿,待补充完善,欢迎大家一起交流. 转载请声明出处:http://blog.csdn.net/yangbutao/article/details/12242441 作者:杨步涛 关注分布式架构.大数据.搜索.开源技术 QQ:306591368 技术Blog:http://blog.csdn.net/yangbutao 一. 设计理念 1.      空间换时间 1)      多级缓存,静态化 客户端页面缓存(http header中包

高并发电子商务平台技术架构

原文出自:http://blog.csdn.net/yangbutao/article/details/12242441 http://stamen.iteye.com/blog/1525924 我自己的大型B2B和B2C站点原来也是用Hibernate,可是后来不得不换成mybatis, 第一是用Hibernate 因为它封装得太高了.非常多东西是隐式进行的.常常引起问题,非常难定位.毕竟凡事有利必有弊: 第二大型站点肯定不是一个数据库.这点Hibernate是非常麻烦的,用Jdbc或Myba

构建高并发高可用的电商平台架构实践(上)

构建高并发高可用的电商平台架构实践(上) 一. 设计理念 1.      空间换时间 1)      多级缓存,静态化 客户端页面缓存(http header中包含Expires/Cache of Control,last modified(304,server不返回body,客户端可以继续用cache,减少流量),ETag) 反向代理缓存 应用端的缓存(memcache) 内存数据库 Buffer.cache机制(数据库,中间件等) 2)      索引 哈希.B树.倒排.bitmap 哈希索

构建高并发高可用的架构

从各个角度总结了电商平台中的架构实践,由于时间仓促,定了个初稿,待补充完善,欢迎大家一起交流. 转载请声明出处:http://blog.csdn.net/yangbutao/article/details/12242441 作者:杨步涛 关注分布式架构.大数据.搜索.开源技术 QQ:306591368 技术Blog:http://blog.csdn.net/yangbutao 一. 设计理念 1.      空间换时间 1)      多级缓存,静态化 客户端页面缓存(http header中包

Java与Netty实现高性能高并发

1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用.相比于传统基于Java序列化+BIO(同步阻塞IO)的通信框架,性能提升了8倍多. 事实上,我对这个数据并不感到惊讶,根据我5年多的NIO编程经验,通过选择合适的NIO框架,加上高性能的压缩二进制编解码技术,精心的设计Reactor线程模型,达到上述性能指标是完全有可能的. 下面我们就一起来看下N

构建高并发高可用的电商平台架构实践 转自网络

从各个角度总结了电商平台中的架构实践,由于时间仓促,定了个初稿,待补充完善,欢迎大家一起交流. 转载请声明出处: 作者:杨步涛 关注分布式架构.大数据.搜索.开源技术 QQ:306591368 技术Blog:http://blog.csdn.net/yangbutao 一. 设计理念 1.      空间换时间 1)      多级缓存,静态化 客户端页面缓存(http header中包含Expires/Cache of Control,last modified(304,server不返回bo

Java异步NIO框架Netty实现高性能高并发

1. 背景 1.1. 惊人的性能数据 近期一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用.相比于传统基于Java序列化+BIO(同步堵塞IO)的通信框架.性能提升了8倍多. 其实,我对这个数据并不感到吃惊,依据我5年多的NIO编程经验.通过选择合适的NIO框架,加上高性能的压缩二进制编解码技术,精心的设计Reactor线程模型,达到上述性能指标是全然有可能的. 以下我们就一起来看下Ne

电商产品开发这些年,遇到的高并发架构坑儿

电商产品开发这些年,遇到的高并发架构坑儿 宇宙通用的镜子 百家号|03-04 14:21 关注 前言 为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合自己业务场景的高并发处理方案. 在电商相关产品开发的这些年,我有幸的遇到了并发下的各种坑,这一路摸爬滚打过来有着不少的血泪史,这里进行的总结,作为自己的归档记录,同时分享给大家. 服务器架构 业务从发展的初期到逐渐成熟,服务器架构也是从相对单一到集群,再到分布式服务. 一个可以支持高并发的服

大数据高并发系统架构实战方案(LVS负载均衡、Nginx、共享存储、海量数据、队列缓存)

课程简介: 随着互联网的发展,高并发.大数据量的网站要求越来越高.而这些高要求都是基础的技术和细节组合而成的.本课程就从实际案例出发给大家原景重现高并发架构常用技术点及详细演练. 通过该课程的学习,普通的技术人员就可以快速搭建起千万级的高并发大数据网站平台. 亮点一:真实环境还原,课程采用了VM环境重现大网站集群服务器环境,真实环境还原再现. 亮点二:基础实用,细节决定成败,课程内容在演练过程中重点介绍各种细节,保证初级人员快速入门及高级进阶. 亮点三:讲师丰富的海量平台运作经验 讲师tom5多