1.如何抗住双11一天几十亿的订单量?JVM该如何设置内存?
我们先来看一个数据,2020天猫双11全球狂欢季实时物流订单总量定格在23.21亿。这是什么概念!一天成交23.21亿个订单!更夸张的是天猫订单在2020年创建峰值达58.3万笔/秒!
54.4 万笔/秒订单背后的秘密
双11前两个月,阿里巴巴完成了将数以十万计的物理服务器从线下数据中心迁移到云上。这是一个浩大的工程,但前端的消费者毫无感知。
阿里云近三年投入巨大资源研发出来的神龙服务器,是54.4万笔/秒订单的峰值能够平稳度过的保障。54.4 万笔/秒订单是什么概念?阿里云智能基础产品事业部研究员张献涛表示,其他公司可能还在为1000笔/秒的订单做斗争。
不可忽视的算力
双11当天,阿里巴巴处理了970PB的数据。一个可以对比的数字是,央视拍了几十年的节目,存下来的数据是80PB。
支撑双11大规模算力的是流计算系统和飞天大数据平台。在系统和商家调度上,流计算系统发挥了重要作用。比如,双11当天商家会提前备货,当预测商家主推的商品卖得太快时,飞天大数据平台会给商家提示改变一下策略,不要开场就缺货;当预测主推的商品销量达不到预期时,飞天大数据平台会提醒考虑商家发优惠券拉动销量。
不一样的双11
在今年双11的媒体沟通会上,阿里巴巴集团CTO张建锋表示,阿里云在技术上完成了四个方面核心突破:
第一、在核心虚拟机系统上,自研神龙架构,用自研的服务器来做虚拟化。神龙服务器在压力很大的情况下,输出也是非常线性的。
第二、自研了云原生的数据库,今年双11上,没有任何问题。
第三、计算与存储做了分离,数据都是从远端存取的,存储可以很方便的扩容。
第四、做了RDMA网络,能够做到在远端存储,能够比本地读写磁盘更快。
双11期间,近200万个容器支撑着电商的核心系统,在商家侧阿里巴巴的技术团队为商家快速的扩容了5.4万核,峰值每秒帮助商家处理87万笔订单,向商家提供了410亿次的调用。
这些都是双十一背后的技术力量。
2.每日百万的支付系统应该如何设置JVM内存?
现在的系统基本很多都离不开支付,那么支付系统的开发也几乎是我们必须掌握的一个技能,这里我们以一个电商系统的开发为背景,针对一个日交易量在百万的支付系统作为一个实战案例来进行分析。
针对双11这样一天的订单量显然不仅仅是靠控制JVM内存和服务器台数就能解决的,这里笔者以平均每日百万量的交易支付订单来做实战案例分析,注意:每日支付订单量能达到百万的也基本上是现在国内最大的互联网公司,或者是一个通用型第三方支付平台,对接各种APP的支付交易。
进入正题分析:
首先大家正常上网购物的流程是:添加商品到购物车 → 下单 → 结算支付 → 显示支付结果
而对于我们的系统开发来讲整个过程应该如何进行呢?我们先来看一张之前开发我做的一张微信支付的全流程图:
是不是感觉有点复杂?而且这里出现了三个系统的交互,我们将上图再精简一下,那么其实整个过程就是如下这样的:
这样一提取是不是感觉非常清晰了?
订单系统你可以理解为就是我们的电商后台系统,而我们的支付系统也可以看做是电商系统中的一部分,不过这部分却非常重要,通常我们提取出来单独作为一个独立的系统进行开发和维护。
支付系统是连接消费者、商家(或平台)和金融机构的桥梁,管理支付数据,调用第三方支付平台接口,记录支付信息(对应订单号,支付金额等),金额对账等功能。
我们把整个流程从用户到订单系统到支付系统以及三方支付系统的流程先梳理下,大家先有个整体的支付逻辑:
- 用户提交订单支付到我们的订单系统
- 订单系统将该支付请求提交给支付系统
- 支付系统生成一个支付订单,此时订单状态是“待支付“状态,返回用户跳转到付款页面,选择支付方式
- 用户选择微信or支付宝确定付款方式
- 支付系统把实际支付请求提交到第三方支付渠道:处理请求、资金转移
- 返回处理结果,支付系统修改订单为“已完成"
以上仅仅是我们简化的一个简单支付流程,实际一个完整支付系统还包含很多东西(比如账户管理、对账管理、清算管理、结算管理等),我们重点关注核心的支付流程即可。
一个每日百万交易的支付系统压力在哪里?
首先通过上述的流程图我们知道,用户提交支付请求,到订单系统提交真正的支付订单到支付系统,那么这个时候支付系统才算是真正开始工作以及处理,那么每天有一百万个支付请求对应着支付系统就会接收到一百万个支付订单,从而要去存储和处理这一百万个支付订单,那么说的更直白点就是我们的JVM内存中每天会有百万个支付订单对象需要创建(每个订单对象包含用户信息、商品信息、支付渠道信息、支付时间、价格等各类信息的汇总),因此我们聚焦在JVM的管理中来看,每一天我们的JVM内存中就会有上百万个支付订单对象的创建和销毁,那么我们需要思考以下几个核心问题:
- 我们的JVM内存空间需要多大才能支撑起这么多订单对象的创建?堆内存空间是关键,又该分配多少?
- 每台机器需要多大的内存空间,以及需要部署多少台机器?
明确了两个核心问题后,我们想要去分析和确定内存的分配,就必须知道我们的系统高峰期在哪儿?因为一般来讲用户在购物的时候都会有个峰值进行购买,比如中午和晚上,统计在一起也就大概几个小时,也就是说在几个小时内就能产生差不多一百万个订单,我们按照4个小时来计算差不多在60~70笔订单/秒,这里我们直接取整,按照每秒有100笔订单产生来进行计算和处理。
一个支付订单的处理需要多久以及占用多大空间
接下来我们必须要清楚的知道一个订单大概要处理的时间,当用户点击提交订单的时候,这时会携带订单相关参数到电商后台系统,由电商系统创建订单,并做移除购物车商品的操作,以及保存订单到数据库的操作等,接着才会向支付系统发送当前订单,整个过程从发起请求到创建订单到支付系统中,我们粗略计算为1秒差不多了。
那一个订单所占据的对象大小是多少呢? 一般一个订单对象中核心的实例变量也就20多个差不多了,根据基本数据类型所对应的字节大小来计算,一般一个订单对象也就差不多在500字节的大小。那每秒100笔订单到来,也就是差不多一秒能产生 100*500 = 50000大概也就50KB而已,其实非常的小。
那么结合以上两点分析我们可以知道,系统每1秒会来100个支付订单,而每个支付订单的创建需要1秒,那也就是1秒过后,就会在内存中产生50KB的垃圾对象,因为1秒过后这100个对象就没人引用了,成为新生代中的垃圾对象了。
下一秒过后又会持续产生100个订单对象,那么接着又继续产生50KB的垃圾对象,如此一来新生代里就会持续的产生堆积垃圾对象,直到装满为止触发Minor GC进行回收。
支付系统内存占用预估
按照上述所分析,1秒产生50KB垃圾对象,那么100秒就有差不多5MB垃圾对象了,可能大家觉得有点不足为惧,但是我们以上仅仅只是分析了一个支付订单对象的占用大小,实际运行中每秒还会产生其他大量的对象(系统本身的+我们携带关联的各种对象),所以我们真正要估算内存占用的话,还得将之前的计算结果放大10~20倍!
那这样估算的话,我们每秒钟创建的对象大概就在500KB~1MB之间。按最大1MB来计算好了,1秒产生1MB垃圾对象,那100秒就能产生出来100MB垃圾对象,按新生代内存为1个G来计算,Eden区分配800MB,那也就是800秒就得触发一次Minor GC了,如果频繁的触发Minor GC肯定不是一个好事!会影响我们线上的性能稳定。
支付系统JVM内存如何设置?
那我们在真正系统上线的时候应该如何进行部署以及分配JVM内存呢?这里假如我们经济有限仅仅分配一台2核4G的机器来部署,4G的内存能真正分配到JVM上的也就最多2G,而这2G还不能全都给堆内存,还有方法区、栈内存等区域,堆内存最多也就能分配到个1G左右,而且堆内存还分新生代和老年代,这样算下来我们的新生代最多也就几百Mb大小了,根据我们之前的分析,1秒就能消耗1MB左右的内存,几百秒就能撑满导致垃圾回收,影响我们系统的性能稳定性。(一旦触发垃圾回收就会导致STW,系统线程停止,这块我们后续会讲解)
那么如何解决和优化呢?
- 可以考虑提升成本,使用4核8G的机器来进行部署,那么我们的JVM至少可以分配到4G以上内存,新生代也至少能分配到2G内存,那么可以将Minor GC的触发时间由几百秒提升到半小时~1小时触发,降低GC的频率
- 扩展服务器数量,我们可以部署3~5台机器来进行横向扩展,当然机器数量越多,每天机器处理的请求就更少,这样对JVM内存的压力就更小。
当然实际需要根据各位自己的业务量以及系统性能进行合理配置,针对每个系统上线前都要做一次JVM内存的模拟估算(如何通过工具来查看实际JVM内存的变化过程后续我们再讲解)并且是多次测试得出一个合理的数据再通过预估的用户请求量来进行模拟估算,提前设置有一个合理的值,减少GC的频繁触发,保障系统的稳定运行。
3.双11大促,瞬时访问量增加10倍
除了日常的平均支付交易量需要预估设置以外,还需要思考的就是大促的时候如何保障服务器的稳定。比如双11来临,很可能导致服务器压力瞬间增大10倍,都在这一时段或这一天来买东西了,那么这个时候可能计算出来的就不是每秒100笔支付订单的问题了,可能是每秒1000笔订单甚至更大!这个时候就不光是我们的内存压力大,特别是线程资源,CPU资源几乎都会占满,内存也是岌岌可危!
之前我们计算过每一秒产生的对象是在1MB,那遇到大促的时候,每一秒的内存占用有可能就能达到10MB甚至几十MB(得往大一点预估不要考虑刚刚好),并且这个时候还有个问题就是,以前差不多1秒能处理完我们100个订单,但是现在1000个订单1秒是肯定处理不完的,刚才也分析过,CPU、线程、内存都吃紧,系统性能也会跟着不稳定,那1000个订单至少需要几秒甚至几十秒才可能处理完毕。
那么当我们的新生代快满的时候,这个时候还在往里进对象就会出现问题,因为上一波的对象可能还未处理完这时有来一波对象,而新生代中也被垃圾对象给填满了,那么就会触发Minor GC,假设我们的新生代和老年代内存分配分别为1G,现在的情况如下:
新的请求过来分配空间不足触发MinorGC,回收部分对象,而我们的少部分对象由于系统处理较慢还在引用,而每秒还在产生大量对象不断进来,假如我们预估每秒创建新对象100MB,1个G的新生代中Eden区占800MB,不到8S就会触发一次MinorGC,那么这么大量频繁的触发MinorGC,加上少数对象处理较慢就会导致部分对象由于多次经历Minor GC后依然存活,最终进入老年代:
而当那部分对象处理完毕后失去引用就成为垃圾对象了,但是已经存在于我们的老年代了。
那么按照这个频率老年代被占满的速度也很快!而一旦老年代被占满就会触发Full GC,这个比Minor GC更恐怖,导致系统暂停的时长更久!你试想下,在大促秒杀抢单的过程中你的系统卡死,正在执行垃圾回收,而用户这边一直无法进入付款页面是什么感受? 等系统恢复,再进行付款这时也过秒杀时段或商品已售空,严重影响用户体验。
至于新生代和老年代的垃圾回收规则以及如何优化我们放在后续讲解。
因此大家在公司进行项目开发上线的时候一定要结合JVM内存进行思考和预估,特别是用户量大的项目,不合理的预估业务系统压力,等真正压力来临的时候,系统随时面临崩盘。这也是为什么很多大厂面试都要考核JVM这块的原因,考核你是否真正做到对你自己的系统足够了解,对线上的内存预估是否准确。