8、案例实战【处理百万级交易无压力】:支付系统JVM调优实战指南

8.1、前文回顾

本文将以一个日交易量达百万次的支付系统为背景,为大家深入分析在上线部署一个系统时,如何根据系统的业务量来合理设置JVM的堆内存大小。

通过阅读之前的文章,相信大家已经对编写的代码如何在JVM中运行的基本原理有了一定的了解,同时也知道如何通过参数来设置JVM各个内存区域的大小。然而,仅仅依靠之前的文章,当大家自己部署一个线上系统时,可能会感到困惑,不知道如何为线上系统的JVM内存大小进行合理的设置。

因此,我将通过一个实际案例进行分析,帮助大家学会如何为自己所负责的线上系统合理设置JVM内存大小。

8.2、案例系统背景

先来看看,如果在一个电商系统里,一个支付系统大概应该是一个什么样的位置,如下图。
在这里插入图片描述

在网上购物的流程,对于大多数人来说,都是非常熟悉的。通常,我们在一个APP或者网站上购买商品时,都会经历以下步骤:首先,我们会浏览商品,将心仪的商品添加到购物车中;然后,我们会下单,即确认购买的商品和数量;最后,我们会进行支付,将款项从我们的账户划拨到商家的账户。

这个过程,可以通过上面的图来清晰地展示。在这个图中,支付系统是网站或者APP后台系统中非常重要的一部分,它负责管理公司的资金流。

支付系统的工作,就是处理用户的支付请求。当用户选择了一种付款方式,比如微信或支付宝,支付系统就会与这些第三方支付渠道进行对接。

以微信支付为例,如果用户通过微信支付了188元,那么这188元就会从用户的微信钱包中划出,转入电商公司的账户。在这个过程中,支付系统就需要与微信支付渠道进行对接,完成资金的划拨。

总的来说,无论是在APP还是网站上购物,其基本的流程都是:选择商品,添加到购物车,下单,支付,资金划拨。而支付系统,就是在这个过程中,负责处理用户的支付请求,以及与第三方支付渠道的对接。

8.3、核心业务流程

接着我们来讲一下支付的核心业务流程,大家先看下面的图。
在这里插入图片描述

通过上述步骤的详细描述,我们可以清晰地理解整个支付流程。

首先,用户在我们的商城系统中提交一个订单支付请求。然后,商城系统将这个请求转发给支付系统,由支付系统生成一个待支付的订单。

接下来,支付系统会引导用户跳转到付款页面,用户在这里选择一种付款方式。

用户在选择了付款方式后,发起实际的支付请求。支付系统接收到这个请求后,将其转交给第三方支付渠道,例如微信或支付宝,由这些渠道处理支付请求并进行资金转移。

一旦微信或者支付宝完成了支付处理,它们会将支付结果返回给支付系统。支付系统收到结果后,会更新本地的支付订单状态为“已完成”。

当然,一个完整的支付系统还包含许多其他功能。例如,它还需要负责对账以及与合作商户之间的资金清算。支付系统需要包括应用管理、账户管理、渠道管理、支付交易、对账管理、清算管理、结算管理等各种功能模块。然而,在这里,我们主要关注的是最核心的支付流程。

8.4、每日百万交易的支付系统的技术挑战

接下来,我们来深入探讨一下,一个日处理百万交易的支付系统,其压力主要集中在哪里。

以我们的核心支付流程为例,这个系统每天需要处理百万笔交易。一般来说,能够达到这种规模的交易数量,要么是国内顶尖的互联网公司,要么是一个通用的第三方支付平台,负责处理各种APP的支付交易。

通过上文的分析,我们可以明确看到,在业务流程中,最关键的环节就是当用户发起支付请求时,系统会生成一个支付订单。这个支付订单需要详细记录诸如:谁发起的支付?支付的商品是什么?通过哪个渠道进行支付?支付的时间是什么时候?等等,诸如此类的信息。

如果每日有百万笔交易,那么从我们JVM的角度来看,就意味着每天会在JVM中创建上百万个支付订单对象。

仔细想想,是不是这样的呢?如下图:
在这里插入图片描述

我们的支付系统面临着多方面的挑战,包括处理高并发访问、高性能请求处理以及存储大量支付订单数据等技术难题。

然而,如果我们暂时不考虑这些系统架构层面的因素,仅从JVM(Java虚拟机)的角度来看,我们支付系统最大的压力在于每天需要在JVM内存中频繁地创建和销毁100万个支付订单。这就引出了一系列核心问题:

  1. 我们需要部署多少台机器来支撑我们的支付系统?
  2. 每台机器需要配置多大的内存空间?
  3. 在每台机器上运行的JVM需要分配多大的堆内存空间?
  4. 为了确保能够支持如此大量的支付订单在内存中的创建,而不会导致内存不足进而引发系统崩溃,我们应该为JVM分配多大的内存空间?

这些就是本文需要深入探讨的核心问题。

8.5、每秒究竟有多少支付在幕后飞奔?

为了合理设置线上系统中的关键参数,即JVM堆内存大小,我们需要首先确定一个关键指标:系统每秒需要处理的支付订单数量。

假设系统每天需要处理100万个支付订单。通常情况下,用户的交易行为集中在每日的高峰时段,例如中午或晚上。

假设每天的高峰时段持续几个小时,我们将100万订单平均分配到这些小时内。这样,我们可以估算出在高峰时段,大约每秒钟需要处理100笔订单。为了计算方便,我们以每秒100笔订单作为基准进行计算。

现在,设想我们的支付系统部署了3台服务器。在这种情况下,每台服务器实际上每秒需要处理的订单数量大约是30笔。

大家看下面的图,这个图可以反映出来支付系统每秒钟的订单处理压力。
在这里插入图片描述

8.6、订单处理时间究竟需要多久?

下一个问题,我们需要搞清楚的是,处理每个支付订单需要多长时间。

当用户发起一次支付请求时,系统需要在JVM中创建一个支付订单对象,将相关数据填充进去,然后将这个支付订单写入数据库,可能还会进行一些其他处理。

假设一次支付请求的处理,包括创建支付订单,大约需要1秒钟的时间。

那么,你可以想象一个模型,每台机器每秒接收30笔支付订单的请求,然后在JVM的新生代中创建了30个支付订单对象,并进行写入数据库等操作。

1秒后,这30个支付订单就处理完毕,然后这些支付订单对象的引用就被回收了,这些订单在JVM的新生代中就成为了无人引用的垃圾对象。

接下来的每一秒,都会重复这个过程,接收30个支付订单,进行处理。

8.7、您的每一笔支付究竟需要多少内存空间?

接下来,我们将计算每个支付订单对象所需的内存空间大小。

在之前的文章中的一个思考题中,我们已经教过大家如何进行这个计算。实际上,如果不考虑其他因素,你可以直接根据支付订单类中的实例变量的类型来进行计算。

假设我们有以下的支付订单类,你需要记住,一个Integer类型的变量数据占用4个字节,一个Long类型的变量数据占用8个字节。对于其他类型的变量数据,你可以通过网络搜索来查找它们各自占用的字节数。

public class PayOrder {
    private Integer userId;
    private Long orderTime;
    private Integer orderId;
}

通常情况下,对于像支付订单这样的核心类,我们可以预计它包含大约20个实例变量。在内存中,一个对象的大小通常只有几百字节。为了保守估算,我们可以假设一个支付订单对象占用的内存空间为500字节,这还没有达到1KB。

8.8、支付操作究竟消耗了多少宝贵内存?

之前说过,假设有3台机器,每秒钟处理30笔支付订单的请求,那么在这1秒内,大家都知道,肯定是有方法里的局部变量在引用这些支付订单的,如下图:
在这里插入图片描述

那么30个支付订单,大概占据的内存空间是30 * 500字节 = 15000字节,大概其实也就15kb而已。其实是非常非常小的。

8.9、动态的分析一下支付系统

现在我们已经对整个系统运行的关键环节数据进行了详细的分析,大家可以尝试在脑海中构建一个模型。设想每秒有30个支付请求,相应地,我们需要创建30个支付订单对象,这仅仅占用了KB级别的内存空间。

然后,经过1秒的处理,这30个支付订单对象就不再被引用,它们变成了新生代中的垃圾对象。

在接下来的每一秒,我们的系统都会持续地创建新的支付订单对象,不断地将30个新的支付订单对象放入新生代中,这使得新生代中的对象数量持续累积和增加。

随着时间的推移,我们可能会发现新生代中的对象数量已经达到了几十万个,此时,它们已经占据了几百MB的空间,新生代的空间可能即将被填满。

一旦达到这个临界点,就会触发Minor GC,它会清理掉新生代中的垃圾对象,释放内存空间,然后我们可以继续在内存中分配新的对象。

这就是我们业务系统的运行模型。

8.10、支付系统内存占用预估

在之前的分析中,我们主要关注了核心业务流程中的一个支付订单对象。然而,这仅仅是整个支付系统中的一小部分。在实际的线上运行环境中,支付系统每秒钟会创建大量的其他对象。为了更准确地估算整个支付系统的内存占用情况,我们需要结合访问压力和核心对象的内存占用来进行评估。

具体来说,我们可以将之前的计算结果扩大10倍至20倍。这意味着,除了支付订单对象外,每秒钟还会有其他数十种对象被创建。这些对象中,有一部分会被栈内存的局部变量引用,它们所占用的总内存空间大约在几百KB到1MB之间。

随着时间的推移,每一秒钟都会有大约1MB的新对象被创建并存储在新生代中,然后在下一秒钟成为垃圾。经过多次这样的循环,新生代中的垃圾会逐渐积累,最终触发Minor GC来清理这些垃圾。这就是一个完整系统的大致JVM层面的内存使用模型。

通过这种方式,我们可以更全面地了解支付系统的内存占用情况,从而为系统的优化和调整提供有力的依据。

8.11、如何精准设置JVM堆内存?

在深入分析了支付系统的核心业务流程之后,我们就能清晰地理解到,在每个机器上部署这样的线上系统时,应该如何合理地设置JVM的堆内存。

一般来说,这种在线业务系统的机器配置,常见的是2核4G,或者是4核8G。如果我们选择2核4G的机器来部署,那么可能会有些紧凑,因为虽然机器有4G内存,但是机器本身也需要占用一部分内存,所以JVM进程最多只能使用2G内存。这2G内存还需要分配给方法区、栈内存和堆内存,所以堆内存可能最多只有1G多的内存空间。然后堆内存还分为新生代和老年代,老年代需要放置系统的一些长期存活的对象,所以需要几百MB的内存空间,那么新生代可能也就只有几百MB的内存了。

这样看来,我们上述的核心业务流程,其实只是针对一个支付订单对象来分析的,但是如果我们将这个分析扩大10倍~20倍,预估整个系统的内存使用情况,我们会发现,大致每秒会占据1MB左右的内存空间。那么如果新生代只有几百MB的内存空间,是否会导致运行几百秒后,新生代内存空间就满了?此时是否需要触发Minor GC?

实际上,频繁触发Minor GC会影响在线系统的性能稳定性,具体原因我们后续再详细讨论。这里大家首先要明白的一点是,频繁触发GC并不是什么好现象。

因此,你可以考虑采用4核8G的机器来部署支付系统,这样JVM进程至少可以分配到4G以上的内存,新生代在里面至少可以分配到2G内存空间。这样就可以做到可能新生代每秒多1MB左右的内存,但是需要将近半小时到1小时才会让新生代触发Minor GC,这就大大降低了GC的频率。

例如:我们可以采用4核8G的机器,然后将-Xms和-Xmx设置为3G,给整个堆内存3G内存空间,-Xmn设置为2G,给新生代2G内存空间。而且,如果你的业务量更大,你可以考虑不只部署3台机器,可以横向扩展部署5台机器,或者10台机器,这样每台机器处理的请求更少,对JVM的压力更小。

8.12、本文总结

本文从支付系统的实际案例入手,详细分析了在日百万交易压力下,部署3台机器时的性能表现。我们计算了每台机器每秒需要处理的订单数量,每笔订单的处理时间,以及每秒钟JVM所需的内存空间。通过横向扩展,我们可以预估整个系统每秒所需的内存空间。

进一步地,我们根据数据模型推算出,在不同机器配置下,新生代的内存空间大小。接着,我们分析在不同新生代大小下,触发Minor GC的频率。

为了避免频繁的垃圾回收(GC),我们需要选择合适的机器配置,部署适量的机器,并为JVM堆分配合适的内存空间,以及为新生代分配合适的内存空间。

根据这套配置,我们可以推算出整个系统的运行模型。例如,每秒钟在新生代创建多少对象,1秒后这些对象成为垃圾。我们还可以估算系统运行多久后,新生代会触发一次GC,以及GC的频率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无法无天过路客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值