您的位置 首页 java

从java性能调优的角度分析为什么我们要用kubernetes

本篇文章共计1753字,阅读约需5分钟,建议先收藏再看。

在实际的生产环境中,java服务常常会遇到因fullGC导致的服务不定期出现卡顿的情况,这种情况的发生,也许是代码的责任,也有可能是硬件环境的更新对服务带来意料之外的影响。这里我们针对一次线上的问题,来利用 kubernetes 给出一种行之有效的解决方案。

案例

线上一个15w qps的搜索类服务,在更换为64位的centos系统后,出现长时间的不定期卡顿。为了尽可能的利用硬件资源,该服务通过-Xms和-Xmx将Java堆固定在20GB,

通过监控服务的的运行状况,最终定位到导致服务失去响应的原因是GC停顿导致,虚拟机一次fullGC需要回收20GB的堆,一次FullGC的停顿时间达14秒。此外,还有大量的文档 序列化 产生的大对象,这些对象直接进入老年代,而没有在MiniorGC中被清理掉,也导致了内存快速被消耗殆尽,由此引发了不定期的出现十几秒的停顿,导致显示服务几乎不可用。

这里不对问题进行过多的复盘,我们着重分析一下如何能够有效的避免此类问题。先抛开程序代码问题,在硬件升级前,使用32位系统5GB的堆,服务只是会偶尔出现查询缓慢,但不会出现明显的停顿。而升级硬件环境的初衷,就是为了能够“用空间换时间”,提升服务质量。

在高性能硬件上部署程序的方式,目前主要由两种,通过64位上使用大内存来解决。但如果一台节点划分过多的内存给一个应用,又显得浪费。第二种方式就是建立逻辑集群来利用硬件资源。

在此案例中,使用的是第一种方案,但分配的堆大小并不满足服务所需。使用该方案的前提就是有把握把应用程序的FullGC频率控制的足够低,至少不会影响用户的使用,譬如一天出现一次,这样可以控制服务在深夜时触发Full GC来保证内存在稳定可用的范围内。

除此以外,对于使用大内存的服务来说,我们还要面对其他一些问题,如:

  1. 如果出现堆溢出的情况,调试将成为一个问题,因为堆转储快照可能会非常大,以本例为例,可能会产生十几GB的Dump文件,分析起来非常困难。
  2. 对于搜索服务来说,数据的召回耗时应控制在ms级,对于长达几秒甚至十几秒的停顿,几乎是灾难性的。

鉴于这些问题,我们在实际生产过程中,使用了第二种方案来接决这个问题,解决问题的思路是将搜索服务进行拆分,将内存占用分散到多个服务实例中,解决的办法就是采用kubernetes对搜索服务进行拆分,将服务拆分成多个子模块中。

从架构上分析

在分拆前分服务模型:

在服务分拆前,内存使用大户是proxy服务和检索服务中的S部分。Proxy服务中,包含了流量分发模块和业务逻辑模块。其中业务逻辑模块设计大量的序列化大对象的生成,其中部分大对象直接进入老年代,没有被新生代的GC中被清理掉。而在检索服务中,分词服务本身会加载词典到内存,本身会占用大量内存,且大多数都不是朝生夕灭的内存,因此这部分内存也成为了Full GC的主力。

针对这两种情况,考虑对原有的服务架构做分拆。分拆后的服务模型如下:

分拆后,利用kubernetes的特性,将proxy和检索服务分别容器化并作为pod部署在集群中。其中proxy层服务将路由转发和业务逻辑分离,而检索服务中将分词服务单独拆分出来进行部署。一方面,分拆后,内存的压力得以分散,无需再为大段内存而苦恼。另一方面,整个架构更为清晰,各个部分的扩展与维护的效率都大大加强。第三,整个架构的部署也更为灵活,我们可以根据集群的负载情况,对各模块进行弹性伸缩,如在流量较大时,扩大业务逻辑层服务的副本数,分摊压力。也可以根据模块的特性,决定其部署的方式,如业务逻辑层服务和检索服务,我们尽量避免相同服务同时部署在同一节点,因为一旦某一层级的负载升高时,如果该层级的服务部署在同一节点,那么势必造成该节点的负载压力成倍增加。

此外,为了避免各服务间的资源竞争,借助kubernetes的存储特性,对于有状态的服务,我们选择将各pod的数据挂在到指定路径下,读写各自文件,避免各节点对磁盘的全局竞争。

总结

通过此次的架构升级,各个模块的 堆内存 分配最高不超过8G,有效缓解了FullGC带来的服务停顿时间过长的问题,且FullGC的频率有了明显的下降,可以做到一天至多出现一次的频率。

文章来源:智云一二三科技

文章标题:从java性能调优的角度分析为什么我们要用kubernetes

文章地址:https://www.zhihuclub.com/199937.shtml

关于作者: 智云科技

热门文章

网站地图