Spring Cloud之量化分析应用续租的内存消耗

简介: 量化分析Spring Cloud中Eureka Client一次心跳的内存消耗.

欢迎访问陈同学博客原文
本文修改了三次,感谢 晓波同学帆哥 提的问题、建议与知识帮助。

在分享《Spring Cloud之极端续租间隔时间的影响》 后,晓波同学问:由于心跳频率过高导致出现新对象过多?

索性就试试 量化分析一次心跳带来的内存消耗!本文纯属好奇心驱使,无实际意义。

如何量化分析?

量化分析?那么多干扰因素,怎么个分析法?

测试中有黑盒、白盒测试,而JVM基本是个黑盒,只能通过各性能采集工具或利用JVM自身产生的性能数据来分析。

想起初高中生物对照试验中的 控制变量法,即各对照组中通常设置1各变量,其他条件保持一致。

那就准备一个实验组,一个对照组,先看看变量与影响因子。

变量

  • 心跳频率:第一组心跳频率保持 1s/次,第二组心跳 10分钟/次(在试验期间内不进行心跳)

两组试验唯一不同的就是是否心跳,下面就尽量排除各种干扰因素。

影响因素

  • 测试的机器实时资源情况干扰:同时运行两组试验,确保它们在相同环境下运行
  • GC的影响:尽量在应用启动稳定后(排除启用时的频繁GC)的第一次GC内完成试验,同时Metaspace设置为500M,避免因Metaspace引发Full GC
  • 租约失效时间:设置为10分钟,确保第二组注册到Eureka Server后不会被剔除
  • Fetch registry的影响:将Eureka Client从Server获取所有实例信息的频率设置为10分钟,在试验期间不要fetch registry
  • 性能采集工具影响:采用jstat,jstat是读取JVM写在磁盘上的性能数据,对JVM没什么干扰。

可参考笨神的 JVM源码分析之Jstat工具原理完全解读 ,JVM默认50ms写一次性能数据到磁盘,需要采集数据的直接去获取,可使用 jcmd pid PerfCounter.print来体验下。VisualVM这种图形化工具不方便统计数据。

  • 其他个人认知水平外的未知因素

试验环境

  • JDK1.8
  • Mac OS

希望通过以jstat命令持续打印Eden区内存的变化来采集数据,并结合其他日志作为数据来进行定量分析。

试验准备

将简单的Eureka Client应用打成jar包,以jar包形式同时运行2个应用。为了观察心跳情况,2个应用心跳请求都经过本机nginx代理。

下面为试验简图,两个应用分别跑在8080、8081端口,请求经Nginx代理并转发到Eureka Server。

Client1表示续租频率1s/次,Client600表示续租频率600s/次。

tail -f 查看nginx日志,可以动态、清晰的观察client1的续租请求。

动手做试验

启动应用

两应用的默认配置:

# 排除fetch registry 任务的干扰,仅应用启动时获取一次
eureka.client.registry-fetch-interval-seconds=600
# 租约有效时间为 10分钟
eureka.instance.lease-expiration-duration-in-seconds=600

以下列命令同时运行两个应用,除续租间隔分别为1秒和600秒外,无任何差别。

nohup java -Dspring.application.name=eureka-client1 -Dserver.port=8080 -Deureka.instance.lease-renewal-interval-in-seconds=1 -Xmx100m -Xms100m -XX:NewRatio=1 -XX:MetaspaceSize=500m -verbosegc -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/Users/cyj/test/gc-client1.log -jar eureka-client.jar &

nohup java -Dspring.application.name=eureka-client600 -Dserver.port=8081 -Deureka.instance.lease-renewal-interval-in-seconds=600 -Xmx100m -Xms100m -XX:NewRatio=1 -XX:MetaspaceSize=500m -verbosegc -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/Users/cyj/test/gc-client600.log -jar eureka-client.jar &

收集Nginx日志

日志中1-6序号是我标记的,仅截取了日志的关键属性

1:"GET /eureka/apps/ HTTP/1.1" 200 80
2:"GET /eureka/apps/ HTTP/1.1" 200 80
3:"POST /eureka/apps/EUREKA-CLIENT1 HTTP/1.1" 204 0
4:"POST /eureka/apps/EUREKA-CLIENT600 HTTP/1.1" 204 0
5:"PUT /eureka/apps/EUREKA-CLIENT1/192.168.31.136:eureka-client1:8080?status=UP&lastDirtyTimestamp=1535551985958 HTTP/1.1" 200 0
6:"PUT /eureka/apps/EUREKA-CLIENT1/192.168.31.136:eureka-client1:8080?status=UP&lastDirtyTimestamp=1535551985958 HTTP/1.1" 200 0

启用应用后,

  • 第1-2行是两个应用fetch registry的请求
  • 第3-4行 POST 请求是两个应用首次注册的请求
  • 第5行开始,日志以每秒1次的频率出现,是Client1的心跳PUT请求日志

此时,两个应用已达到预期的运行状态。

收集GC日志

在应用启动后运行一段时间后,查看GC日志(仅截取了启动后的日志)。

client1的日志(序号由我标记,不是GC日志的一部分):

...
1->6.881: [GC (Allocation Failure) 
2->7.152: [GC (Allocation Failure) 
3->7.493: [GC (Allocation Failure) 
4->7.623: [GC (Allocation Failure) 
5->435.604: [GC (Allocation Failure)

在第4行时,应用启动结束,耗时7.623秒。第5行为第一次GC发生的时间。

client600的日志

...
7.129: [GC (Allocation Failure)
7.392: [GC (Allocation Failure)
7.747: [GC (Allocation Failure)
7.903: [GC (Allocation Failure)

在运行一段分钟后,直到我stop应用,client600并未发生GC。

client600 启动时间为 7.903 ,与client17.623 并无太大差异,且GC启动期间两应用发生的GC次数一致。

收集Eden区内存变化

使用 jstat -gc 1000,每秒打印一次

client1 的内存变化如下,仅列出中间部分。下面数据刷新间隔为1秒。

EU 即Eden的使用量 以一定速度,非常规律的变化,其他指标并无变化。

     EC       EU      YGC   YGCT     
...
  45056.0  4729.8  63  0.165
  45056.0  4729.8  63  0.165
  45056.0  4729.8  63  0.165
  45056.0  5056.0  63  0.165
  45056.0  5056.0  63  0.165
  45056.0  5056.0  63  0.165
  46080.0  1273.0  64  0.167
...

再看看client600 的内存变化,仅列出中间部分。

整个试验期间,Eden区只有3次变化,从25993.8到26895.6。

   EC       EU       YGC   YGCT      
...
45056.0  25993.8  63    0.169  
45056.0  25993.8  63    0.169  
45056.0  26895.6  63    0.169  
45056.0  26895.6  63    0.169  
45056.0  26895.6  63    0.169  
...
45056.0  26895.6  63    0.169  
45056.0  26895.6  63    0.169  
45056.0  26895.6  63    0.169  
...

光这么看看不出什么,最后将基于两个试验组在相同时间内的Eden变化来分析数据。

收集threadstack情况

使用jstack收集试验期间线程情况。

除应用运行的必要线程外,并无其他线程在运行。排查无关线程干扰。

数据分析

主要分析jstat得到的数据。EC表示Eden区容量,EU表示Eden区使用量,单位为KB。

client1和client600的EC都是45056.0,在下面的试验数据中未发生变化。

时间 client1 EU client600 EU
开始 30583.0 25993.8(不变)
10s后 31200.1 25993.8(不变)
20s后 31518.5 25993.8(不变)
50s后 32738.6 25993.8(不变)
104秒后 35174.1 26895.6
240秒后 41699.1 27797.1
316秒后 45056.0 27797.1

以上为在应用启动后约5分钟的统计数据(还有很多,5分钟的数据已够分析了,317秒时client1发生了GC), 统计开始时 client1 消耗的内存已比 client600 多。

下面开始分析数据:

  • 试验总耗时:316秒
  • client1内存消耗:45056.0 - 30583.0 = 14473(14.47M)
  • client600内存消耗:27797.1 - 25993.8 = 1803.3(1.8M)

由于client600未进行续租,可以认为 它的内存消耗是应用正常运行的必要消耗

那client1的心跳消耗可认为是:client1内存消耗(14473) - client600内存消耗(1803.3),值为:12669.7

每次心跳消耗的内存=心跳内存消耗 ÷ 心跳耗时。

虽然client向server发送请求也会消耗时间,但由于是本机通讯,我们暂且忽略这个极小的时间消耗。认为316秒发生了316次心跳

下面是nginx部分日志,由于在容器中运行,时间未进行校准。且日志忘了把毫秒打印出来,先忽略这个时间消耗。

[29/Aug/2018:14:13:11 +0000] "PUT /eureka/apps/EUREKA-CLIENT1
[29/Aug/2018:14:13:12 +0000] "PUT /eureka/apps/EUREKA-CLIENT1
[29/Aug/2018:14:13:13 +0000] "PUT /eureka/apps/EUREKA-CLIENT1

试验结果

终于到了看试验结果的时候了。

每次心跳内存消耗=12669.7 ÷ 31640.094KB

虽说心跳是一次HTTP调用,但Eureka Client的调用还是有很多步骤。下面是一张debug中调用栈的截图。

当然,心跳只应用中非常小的一件事情,并不会给应用带来什么影响。 Eureka Client默认续租频率为30秒/次,在实践中根据需要调整即可,一般保持默认就行。

小结

以上纯属个人胡闹,仅在自己的认知水平内做的尝试性试验,且只做了一次试验。试验目的和试验结果意义都不大,当中如有不对之处,还恳请指出。

当今时代,硬件资源早已不是瓶颈,经常是性能不够,机器来凑,工作中遇到的问题更多的是业务开发所产生的BUG,写好CRUD也不是件简单的事情。不过多学点东西,扩展下视野总是好的。


欢迎关注陈同学的公众号,一起学习,一起成长

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
17天前
|
缓存 算法 Java
Java内存管理与调优:释放应用潜能的关键
【4月更文挑战第2天】Java内存管理关乎性能与稳定性。理解JVM内存结构,如堆和栈,是优化基础。内存泄漏是常见问题,需谨慎管理对象生命周期,并使用工具如VisualVM检测。有效字符串处理、选择合适数据结构和算法能提升效率。垃圾回收自动回收内存,但策略调整影响性能,如选择不同类型的垃圾回收器。其他优化包括调整堆大小、使用对象池和缓存。掌握这些技巧,开发者能优化应用,提升系统性能。
|
25天前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
27天前
|
人工智能 关系型数据库 Serverless
Serverless 应用引擎常见问题之AI应用限制人为限制内存如何解决
Serverless 应用引擎(Serverless Application Engine, SAE)是一种完全托管的应用平台,它允许开发者无需管理服务器即可构建和部署应用。以下是Serverless 应用引擎使用过程中的一些常见问题及其答案的汇总:
26 3
|
25天前
|
存储 算法 编译器
【C++ 内存管理 重载new/delete 运算符 新特性】深入探索C++14 新的/删除的省略(new/delete elision)的原理与应用
【C++ 内存管理 重载new/delete 运算符 新特性】深入探索C++14 新的/删除的省略(new/delete elision)的原理与应用
43 0
|
21天前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
42 0
|
2天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
3天前
|
Java API 对象存储
对象存储OSS产品常见问题之使用Spring Cloud Alibaba情况下文档添加水印如何解决
对象存储OSS是基于互联网的数据存储服务模式,让用户可以安全、可靠地存储大量非结构化数据,如图片、音频、视频、文档等任意类型文件,并通过简单的基于HTTP/HTTPS协议的RESTful API接口进行访问和管理。本帖梳理了用户在实际使用中可能遇到的各种常见问题,涵盖了基础操作、性能优化、安全设置、费用管理、数据备份与恢复、跨区域同步、API接口调用等多个方面。
22 2
|
17天前
|
负载均衡 网络协议 Java
构建高效可扩展的微服务架构:利用Spring Cloud实现服务发现与负载均衡
本文将探讨如何利用Spring Cloud技术实现微服务架构中的服务发现与负载均衡,通过注册中心来管理服务的注册与发现,并通过负载均衡策略实现请求的分发,从而构建高效可扩展的微服务系统。
|
24天前
|
算法 Java C++
【C/C++ 内存知识扩展】内存不足的可能性分析
【C/C++ 内存知识扩展】内存不足的可能性分析
12 0
|
25天前
|
消息中间件 Cloud Native 网络安全
云原生最佳实践系列 3:基于 SpringCloud 应用玩转 MSE
该文档介绍了基于云原生应用的产品构建的微服务架构实践。