转转服务瘦身实战

文章目录

    • 1 背景
    • 2 第一步-发现并下掉僵尸服务
      • 2.1 如何发现僵尸服务
      • 2.2 如何下掉僵尸服务
    • 3 第二步-发现并下掉僵尸方法
      • 3.1 如何发现僵尸方法
        • 3.1.1 全量方法的获取
        • 3.1.2 活动方法的获取
        • 3.1.3 ServiceAbility Agent方案详解
          • 3.1.3.1 ServiceAbility Agent使用方法
          • 3.1.3.2 解决stop the world对业务流量的影响
          • 3.1.3.3 采集时机
          • 3.1.3.4 采集节点的选择
      • 3.2 如何删除僵尸方法
        • 3.2.1 全自动删除
        • 3.2.2 手动删除
        • 3.2.3 半自动删除
    • 4 第三步-发现并下掉僵尸组件依赖
      • 4.1 如何发现僵尸组件依赖
      • 4.2 如何下掉僵尸组件依赖
    • 5 总结与成果
    • 参考

1 背景

2023年转转迎来了他的8周岁生日,祝贺转转8岁生日快乐。8岁的人还只是个小朋友,8岁的转转成熟稳重,而许多8岁的代码已经迟暮。

互联网公司的业务有一个特点,那就是快速迭代。许多功能的生命周期非常短暂,这带来3个问题。

  1. 有些服务已经没有业务流量,却仍然占用服务器资源,称之为僵尸服务。
  2. 有些代码已经不再调用,却仍然存在于服务项目中,代码变得臃肿,难以维护、优化,称之为僵尸代码。
  3. 有些组件依赖如MySql、redis、RPC服务等,已经不再调用却仍在连接,称之为僵尸组件依赖。
    针对这3个问题,转转架构部制定了3步走计划,在下文中详细阐述。

2 第一步-发现并下掉僵尸服务

直接下掉一个服务可获取最大回收收益,项目代码可删除,占用的服务器资源可回收。且经过评估,技术难度较低,短期内可获得较大收益。所以把下掉僵尸服务放在了第一步。

2.1 如何发现僵尸服务

僵尸服务是指已经没有业务流量,却仍然占用服务器资源的服务。在转转公司,服务入口流量大致分为以下4种。

  1. 经nginx转发的http/WebSocket流量。
  2. RPC服务流量。
  3. MQ消费。
  4. 定时任务平台调度。
  5. 私有协议流量/服务内部定时任务。

对于前4种流量我们有标准的prometheus监控,可以很容易抓取到。而第5种流量需要RD自定义监控指标,瘦身系统通过自定义的指标抓取监控。
瘦身服务每日从监控平台抓取流量监控,每月1日跑出1个月内无流量的服务,并通知服务负责人预下线通知

2.2 如何下掉僵尸服务

虽然通过技术手段已经确定服务没有流量,但贸然删除服务节点及其代码仍然是不可取的,对线上服务要始终保持敬畏之心。经过仔细评估,我们制定了如下的服务下线流程。在下掉服务节点后15天内如果发现问题仍然可以随时拉起服务,终止下线流程。
下线流程

3 第二步-发现并下掉僵尸方法

删除僵尸方法的收益中等,并不能节省服务器资源,更侧重于防止项目代码腐败。技术难度中等。所以放在了第2步。

僵尸方法就是指长期没有调用的方法,如果想获取僵尸方法的集合,只需要取项目全量方法和活动方法(有调用的方法)的差集,如下图所示。
僵尸方法

3.1 如何发现僵尸方法

3.1.1 全量方法的获取

首先是采用什么技术获取全量方法,经过调研,我们采用了spoon工具扫描项目源码获取全量方法,示例代码如下。

    private static void doScanJavaFile(String javaVersion, File javaFile, List<SourceCodeJavaMethod> sourceCodeJavaMethodList) {
        Launcher launcher = new Launcher();
        launcher.addInputResource(new FileSystemFile(javaFile));
        launcher.getEnvironment().setNoClasspath(true);
        launcher.getEnvironment().setAutoImports(true);
        launcher.getEnvironment().setComplianceLevel(Integer.parseInt((javaVersion.contains(".") ? javaVersion.substring(2) : javaVersion)));
        Collection<CtType<?>> allTypes = launcher.buildModel().getAllTypes();
        for (CtType<?> type : allTypes) {
            String className = type.getQualifiedName();
            for (CtMethod<?> method : type.getMethods()) {
                SourcePosition position = method.getPosition();
                sourceCodeJavaMethodList.add(new SourceCodeJavaMethod(className, method.getSignature(), position.getEndLine() - position.getLine() + 1));
            }
        }
    }

其次是扫描时机。

  1. 在瘦身服务上线时对公司内所有项目源码进行一次全量扫描。
  2. 在服务每次上线完成合并代码到master后再发起一次扫描。
  3. 每周日对公司内所有项目源码进行一次兜底全量扫描。
3.1.2 活动方法的获取

活动方法也就是在jvm运行期间调用过的方法,对活动方法的统计经过调研大致有3种实现方案。

  • Spring AOP
    此方案要求所有需要监控的方法所在的类都是spring bean,对业务代码有侵入性,并且实现复杂度高。

  • java agent字节码增强
    通过在jvm启动参数中加入java agent参数。对源码中的方法进行增强和监控,此方案对业务代码无侵入性,但是实现复杂度高。

  • ServiceAbility Agent
    简称SA,是hotspot虚拟机提供的一种调试工具集,我们常用的jvm命令如jmap、jstack也是采用了该技术。在JVM中,Java代码有两种执行方式,即解释执行和编译执行。JVM会首先进行解释执行,并对解释执行的方法进行计数,超过一定的阈值后则使用jit编译器将字节码编译成本地代码。对于解释执行的方法在SA的Api中用sun.jvm.hotspot.oops.InstanceKlass类表示,而编译执行的方法则以sun.jvm.hotspot.code.CodeBlob类表示。只需要将ServiceAbility Agent attach至进程上,就可以从其api中获取所有的InstanceKlassCodeBlob

3种方法的对比如下:

方案性能损耗代码侵入性实现复杂度
Spring Aop
Java Agent
SA

经过对比发现ServiceAblility Agent展现出无与伦比的优势。SA唯一的问题是当进程被attach后,至采集完成detach期间,整个进程处于stop the world状态,该问题在下文中有详细解决方案。

3.1.3 ServiceAbility Agent方案详解
3.1.3.1 ServiceAbility Agent使用方法

SA在各大版本间不兼容。转转线上有jdk8和jdk17,jdk8中SA以独立jar包的形式存在,位于$JAVA_HOME/lib/sa-jdi.jar,需要手动添加至classpath中,而jdk17不需要。以下为示例代码。

  • 获取InstanceKclass数据
public class KlassVisitor implements SystemDictionary.ClassVisitor {
    private List<CalledMethod> out;
    public KlassVisitor(List<CalledMethod> out) {
        this.out = out;
    }
    @Override
    public void visit(Klass klass) {
        if (klass instanceof InstanceKlass) {
            String className = klass.getName().asString();
            MethodArray methods = ((InstanceKlass) klass).getMethods();
            for (int i = 0; i < methods.length(); i++) {
                Method method = methods.at(i);
                if (method.isNative()) {
                    return;
                }
                long invocationCount = method.getInvocationCount() >> 3;
                if (invocationCount > 0) {
                    String name = method.getName().asString();
                    String signature = method.getSignature().asString();
                    this.out.add(new CalledMethod(className, name, signature, invocationCount));
                }
            }
        }
    }
}
  • 获取CodeBlob数据
public class CodeBlobVisitor implements CodeCacheVisitor {
    private List<CalledMethod> out;
    public CodeBlobVisitor(List<CalledMethod> out) {
        this.out = out;
    }
    @Override
    public void visit(CodeBlob codeBlob) {
        if (codeBlob == null) {
            return;
        }
        NMethod nMethodOrNull = codeBlob.asNMethodOrNull();
        if (nMethodOrNull == null) {
            return;
        }
        Method method = nMethodOrNull.getMethod();
        if (method == null || method.isNative()) {
            return;
        }
        String className = method.getMethodHolder().getName().asString();
        String methodName = method.getName().asString();
        String signature = method.getSignature().asString();
        long invocationCount = method.getInvocationCount() >> 3;
        out.add(new CalledMethod(className, methodName, signature, invocationCount));
    }
}
3.1.3.2 解决stop the world对业务流量的影响

在上文中我们总结了转转公司的4种主要流量入口有经nginx转发的http请求、RPC服务请求、MQ消费、定时任务调度。而这4种流量我们都实现了在进程不结束的情况下调用api进行流量下线的能力。

在流量下线30秒后对jvm进程进行活动方法采集,在采集后重启进程,流量自然恢复。
下掉流量

对于有其他特殊流量的服务,我们提供了手动调用命令进行采集的方案。可由RD自行采用其他方案下掉进程流量,如手动调用接口,通过apollo配置等。在自行下掉流量后可手动调用命令进行活动方法的采集。

3.1.3.3 采集时机

虽然实现了流量下线的能力,并在流量下线30秒后进行采集,但是仍然有某些定时任务的执行时间会超过30秒。为了尽量减少对业务的影响,需要尽量避开长耗时定时任务时间。在最终实现中我们我们允许RD设置每个服务的采集时间,精确至分钟。每分钟运行一次定时任务,对配置该在该分钟内的服务进行采集。

3.1.3.4 采集节点的选择

目前转转每个服务都有1到n个子集群(一组相同启动参数节点的集合),每个子集群的功能略有差异,方法的调用也有所不同,每次采集时从所有子集群中选择1个节点进行采集。
进程的启动时间也是采集时需要考虑的因素之一,我们选择的是(启动时间-30天前的时间戳)取绝对值最小的节点。首先,刚刚启动的节点,方法还没有充分调用,不适合采集;其次启动时间过久的节点,比如1年以上的节点,也不适合采集,因为采集到活动方法可能只在1年前调用过,1年之后没再调用过。

3.2 如何删除僵尸方法

有了全量方法和活动方法,从全量方法集合中减去活动方法集合就得到了僵尸方法。怎样删除僵尸方法也是个需要考虑的问题,大致有3种可供选择的方案。

3.2.1 全自动删除

使用程序全自动删除风险太高,而且有一定的不准确性。不准确性来源于事实上活动方法集合是包含于有用方法集合。某些用的方法可能永远也不会调用到,比如出现某种异常时的兜底方法,如果异常几十年不出现,这个兜底方法几十年都不会有调用,但是这种方法不能删除。某些调用到的方法也会采集不到,比如关闭方法,因为活动方法的采集在进程关闭之前,关闭方法暂时还未调用。

3.2.2 手动删除

由RD到服务瘦身平台上手动查询僵尸方法,并结合业务实际情况,再决定是否删除。该方式准确性高,但是不友好。

3.2.3 半自动删除

我们开发了idea插件,由插件自动扫描出僵尸方法,再由RD结合业务实际情况决定是否删除。该方式效率高,操作友好。最终我们选择了这种方式。
该插件支持设置僵尸时间天数,自动扫描出僵尸方法,并提供快速删除方法按钮。
idea1
idea2

4 第三步-发现并下掉僵尸组件依赖

下掉僵尸组件依赖的收益较低,而现有监控条件不足以满足要求,需要进一步开发,复杂度较高,所以放在了最后一步。

4.1 如何发现僵尸组件依赖

僵尸组件依赖的发现仍然依赖promethues监控,对于某些组件如RPC、codis、rocket mq等,所用的中间件都是转转自研或者二次开发过的,已经提前在中间件中埋入了监控,直接使用现有监控数据即可。而有些组件使用的是开源中间件,无法修改源码。对于开源中间件我们使用java agent技术进行字节码增强加入监控,比如在mysql驱动中加入监控,如下图所示。
image.png

4.2 如何下掉僵尸组件依赖

暂时没有很好的可以自动化或者半自动化下掉僵尸组件的方法,目前我们的做法是检测到僵尸组件依赖后,向服务负责人发送邮件,最终由RD修改代码来下掉僵尸组件依赖。

5 总结与成果

本文详细介绍了转转在服务瘦身方面的技术实现方案,尤其是代码瘦身部分,甚为详细。希望能对读者有所帮助,如遇技术问题可联系转转架构部。

当前该项目收获的成果如下。

  1. 发现僵尸服务功能上线较早,从10月1日至12月20日,共下线服务30个,实例数68个,节省内存246GB
  2. 发现僵尸代码功能上线不久,仍处于试用期,暂未有丰硕成果。但是我们统计了已接入服务的代码利用率,当前综合方法利用率仅43%,行数利用率仅50%,未来可期。
    代码利用率
  3. 发现僵尸组件依赖功能刚刚上线,目前数据量较小,还不足以得出结论。

关于作者

王建新,转转架构部服务治理负责人,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

参考

狂砍千万行代码,零故障!去哪儿网系统瘦身技术揭秘

代码瘦身的设计思想及技术内幕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/296179.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式系统架构设计之分布式缓存技术选型

一、概述 随着互联网业务的快速发展&#xff0c;分布式系统已经成为了解决大规模并发请求、高可用性、可扩展性等问题的重要手段。在分布式系统中&#xff0c;缓存作为提高系统性能的关键技术&#xff0c;能够显著降低数据库负载、减少网络延迟、提高数据访问速度。当面对大量…

XD6500S一款串口SiP模块 射频LoRa芯片 内置sx1262

1.1产品介绍 XD6500S是一款集射频前端和LoRa射频于一体的LoRa SIP模块系列收发器SX1262 senies&#xff0c;支持LoRa⑧和FSK调制。LoRa技术是一种扩频协议优化低数据速率&#xff0c;超长距离和超低功耗用于LPWAN应用的通信。 XD6500S设计具有4.2 mA的有效接收电流消耗&#…

Python基础知识总结2——python中的字符串

python字符串 字符串基本特点空字符串和len()函数转义字符字符串拼接字符串复制不换行打印从控制台读取字符串replace() 实现字符串替换str()实现数字转型字符串使用[]提取字符字符串切片slice操作split()分割和join()合并字符串驻留机制和字符串比较字符串比较和同一性成员操作…

Python如何实现微信支付功能代码示例

微信支付是一种基于互联网的移动支付服务&#xff0c;由中国的即时通讯工具微信提供。用户可以通过微信支付在微信平台上进行在线支付、转账和收款。微信支付支持多种支付方式&#xff0c;包括银行卡支付、微信钱包余额支付、扫码支付等。用户可以用微信支付购买商品、支付账单…

C语言编译器(C语言编程软件)完全攻略(第二十一部分:Code::Blocks汉化教程(附带汉化包))

介绍常用C语言编译器的安装、配置和使用。 二十一、Code::Blocks汉化教程&#xff08;附带汉化包&#xff09; 由于官方下载的 CodeBlocks 全部都是英文版&#xff0c;本教程中给大家推荐的 CodeBlocks 17.12 版本也是官方英文版&#xff0c;所以本节给大家介绍&#xff1a;如…

C# .Net学习笔记—— 异步和多线程(await/async)

一、介绍 1、控制台测试await/async 2、C# 5.0 .Net framework4.5 CLR4.0 以后才有&#xff0c;本身是一种语法糖 二、基本测试 1、不加await测试。 private async static Task TestAsync() {Log.Info($"当前主线程id{Thread.CurrentThread.ManagedThreadId}"…

【六大排序详解】终篇 :冒泡排序 与 快速排序

终篇 :冒泡排序 与 快速排序 1 冒泡排序1.1 冒泡排序原理1.2 排序步骤1.3 代码实现 2 快速排序2.1 快速排序原理2.1.1 Hoare版本代码实现 2.1.2 hole版本代码实现 2.1.3 前后指针法代码实现 2.1.4 注意取中位数局部优化 2.1.5 非递归版本非递归原理代码实现 2.2 特性总结 谢谢阅…

负责任的人工智能与人机环境系统智能

负责任的人工智能是指在人工智能系统的设计、开发、管理、使用和维护过程中&#xff0c;所有相关的角色&#xff08;包括设计者、开发者、管理者、使用者、维护者等等&#xff09;都承担其行为的道义、法律和社会责任。这意味着这些角色需要确保人工智能系统的设计与使用符合伦…

网络安全B模块(笔记详解)- Web渗透测试

Web信息收集 1.通过Kali对服务器场景Linux进行Web扫描渗透测试(使用工具nikto,查看该命令的完整帮助文件),并将该操作使用命令中固定不变的字符串作为Flag提交; Flag:nikto -H 2.通过Kali对服务器场景Linux进行Web扫描渗透测试(使用工具nikto,扫描目标服务器8080端口,…

阻止持久性攻击改善网络安全

MITRE ATT&CK框架是一个全球可访问的精选知识数据库&#xff0c;其中包含基于真实世界观察的已知网络攻击技术和策略。持久性是攻击者用来访问系统的众多网络攻击技术之一;在获得初始访问权限后&#xff0c;他们继续在很长一段时间内保持立足点&#xff0c;以窃取数据、修改…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑多元不确定性和备用需求的微电网双层鲁棒容量规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 这个标题涉及微电网&#xff08;Microgrid&#xff09;的双层鲁棒容量规划&#xff0c;考虑了多元不确定性和备用需求。让我们逐步解读这个标题&#xf…

【软考中级-软件设计师】day1:CPU、数据的表示、校验码

考点分布目录 中央处理单元CPU 练习题 数据的表示 二进制转十进制 练习题 十进制转二进制 练习题 原码 练习题 反码 练习题 补码 练习题 练习题 移码 浮点数 练习题 奇偶校验 练习题 校验码 模2除法 循环冗余校验CRC 练习题 练习题 练习题 奇偶校验码 只…

docker kingbase

docker kingbase run 命令 docker run -tid \ -e ENABLE_CIyes \ -e NEED_STARTyes \ -e DB_MODEoracle \ -e DB_USERkingbase \ -e DB_PASSWORD123456 \ --privileged \ -p 4321:54321 \ -v /home/admin/SoftWare/volume/kingbase/userdata/data:/home/kingbase/userdata/da…

基于seatunnel实现mysql同步clickhouse验证

场景&#xff1a; 需求想要实现mysql同步到clickhouse&#xff0c;seatunnel部署见前面文档linux环境seatunnel安装运行-CSDN博客。 官方说明文档 Clickhouse | Apache SeaTunnel mysql同步配置 server-id1 log_bin/var/lib/mysql/bin.log binlog_formatROW #binlog-do-db 具…

astadmin安装querylist插件Puppeteer

我本来是想在linux服务器上安装&#xff0c;折腾了一天也没安装成功&#xff0c;由于急着用&#xff0c;就先做window10上安装了&#xff0c;以后有时间再研究centos7上安装 一 首先需要安装fastadmin 框架和querylist插件 这个大家可以自行安装&#xff0c;querylist安装地址…

B059-权限管理系统01

目录 知识点介绍项目演示项目搭建动态菜单查询分析(权限表分析)权限系统表分析角色模块pageInfopageHelper实现前端动态分页高级查询新增与修改删除角色 分配权限-表分析角色授权数据-一级和二级权限查询 知识点介绍 项目演示 准备数据库 准备工程auth_new tips&#xff1a;…

三极管组成的光控开关电路原理图

什么是光控开关 光控开关/光控时控器采用先进的嵌入式微型计算机控制技术&#xff0c;融光控功能和普通时控器两大功能为一体的多功能高级时控器&#xff08;时控开关&#xff09;&#xff0c;根据节能需要可以将光控探头&#xff08;功能&#xff09;与时控功能同时启用&…

【QT 自研上位机 与 ESP32下位机联调>>>串口控制GPIO-基础样例-联合文章】

【QT 自研上位机 与 ESP32下位机联调&#xff1e;&#xff1e;&#xff1e;串口控制GPIO-基础样例-联合文章】 1、概述2、实验环境3、 自我总结4、 实验过程1、验证上位机QT程序1、下载样例代码2、修改qt程序3、运行测试验证 2、验证下位机ESP32程序1、下载样例代码2、更改ESP3…

RocketMQ源码 发送消息源码分析

前言 DefaultMQProducer 是默认生产者组件&#xff0c;是生产者客户端中&#xff0c;绝大部分关于生产者和broker、nameSrv进行网络通信的功能入口。其中&#xff0c;包含发送各种形式&#xff08;同步、异步、事务、顺序&#xff09;的消息&#xff0c;针对发送消息部分的实现…

第11章 GUI Page462~476 步骤二十三 步骤二十四 Undo/Redo ②“添加操作”支持“Undo/Redo”

工程二 1.为AddAction类添加Undo() Redo() GetName()成员函数 2.实现AddAction类的Undo() Redo()函数 3.运行效果&#xff0c;但是日志窗口没有记录 原因&#xff1a;AddAction(EditAction* newAction)函数没有实现&#xff0c;另外参数是EditAction类型 所以我们还需要在基…