首页 > 其他分享 >玩转Sermant开发,开发者能力机制解析

玩转Sermant开发,开发者能力机制解析

时间:2023-12-19 11:23:42浏览次数:31  
标签:插件 拦截器 玩转 开发者 日志 Sermant 加载

本文分享自华为云社区《开发者能力机制解析,玩转Sermant开发》,作者:华为云开源 。

前言:

《Sermant框架下的服务治理插件快速开发及使用指南》中带大家一起体验了Sermant插件的开发,快速的了解了Sermant插件开发的全过程,本着从入门到精通的思路,本文对在开发中所常用的能力,从机制上进行更深入的解析。

插件加载&插件调度

解析插件的加载和调度前,可以再回顾一下,Sermant作为一个基于Java字节码增强技术的插件化服务网格,在设计之初就为插件设计了完整的类隔离机制,在《Sermant类隔离架构解析——解决JavaAgent场景类冲突的实践》中进行的详尽的介绍和分析,避免让开发者陷入到复杂的类冲突问题中,从开发者视角来看,可以无需关注类冲突问题,也对Sermant的类隔离机制无感知,同时借助Sermant的局部类加载机制,可以更建议的开发出高性能的服务治理插件。

https://oscimg.oschina.net/oscnet/up-2ce581c1d546617a73942fccc54d035793d.png

图- Sermant类隔离机制

插件加载

既然是开发Sermant插件,最先应该了解的是插件是如何加载和调度的,Sermant的插件化机制中得益于Java 的SPI机制,在很多高可扩展的项目中,都会利用SPI去加载自己的扩展,常见的使用SPI机制的场景包括日志框架、数据库驱动、序列化工具、缓存框架等。

Java SPI(Service Provider Interface)是Java提供的一种服务提供者接口,用于在运行时动态加载实现某个接口或者抽象类的类。通过SPI机制,提供实现的一方可以将自己的实现以插件的形式注入到系统中,而无需修改原有的代码。SPI机制是Java中一种基于接口编程的思想,它提高了代码的可扩展性和灵活性,使得应用程序更加易于扩展和维护。

图- Sermant SPI加载机制

在SPI中有三个关键的要素——接口定义、实现创建、配置文件。在Sermant中,框架中定义插件声明接口用于让插件开发者来定义插件的核心要素,插件开发者只需要按照接口契约,创建自身所需的插件声明实现即可,插件声明接口定义如下:

至于SPI机制中的另一个核心要素——配置文件,则需要开发者在插件声明实现创建完成后资源目录resources中添加META-INF/services目录,并在其中创建名为com.huaweicloud.sermant.core.plugin.agent.declarer.PluginDeclarer的SPI文件,并向其中添加插件声明实现的类名,这样再接入Sermant时,Sermant就可以按照配置的指定来将对应的插件声明加载起来。

插件调度

仅依赖SPI机制是无法支持Sermant强大的框架能力的,在Sermant体系中,每一种不同的治理能力都是一个独立的插件,并且每个服务治理能力的实现,都依赖于多个插件声明的组合和拦截器的组合,通俗来讲,每个插件中都依赖了多个字节码切面来完成完整的服务治理,多插件难免会出现使用相同的切点来执行字节码增强逻辑。在如此情况下,Sermant该如何保证各插件的执行顺序,并且保证不会重复的进行字节码的织入呢。

Sermant在最底层维护了一个切面的调度器,首先在插件加载的过程中,调度器会将插件的拦截器(Sermant中定义服务治理逻辑的组件)通过有序列表进行缓存,当字节码织入点被触发时,会进行拦截器的调度,此时Sermant将模仿方法堆栈的执行方式,先进入的方法后结束:

图- Sermant插件调度逻辑

当在进入目标方法时,调度器将对拦截器按照插件加载的方式执行,这样保证在进入方法时的运行顺序符合方法运行的规律;当执行出目标方法时,调度器将对拦截器按照插件加载顺序的逆序执行,这服务方法堆栈中,方法结束的规律。

基于上述逻辑,通过调度器这一层,可以保证不会对相同目标类目标方法进行重复的无意义的字节码增强,同时保证插件在相同目标的执行逻辑符合方法调用堆栈的逻辑,更符合切面程序的执行风格。并且可以通过控制插件来达到控制拦截器执行顺序的目的,也就是达到了控制插件顺序来控制服务治理生效时机的作用,这对一些特殊场景大有裨益。

开发者相关能力解析

除了插件化和类加载等框架的核心机制,插件开发者更多的需要了解Sermant所提供的一些开发这能力,只有更深入的了解这些能力,才能在服务治理插件开发的时候信手拈来。 插件简单来讲就是一系列切面的集合,最终完成了复杂的治理能力。在面向切面编程时,有两个核心的概念,即Join point(切点)——指定切面的横切位置;Advice(通知)——切面执行的具体行为。对应Sermant的插件开发中也有逻辑与之对应,在Sermant中声明切面位置的称之为插件声明,执行切面逻辑的称之为拦截器。

Sermant的插件声明可以基于类名、超类、注解等进行类定位,并通过方法名、类型、参数、返回值等进行方法定位,通过丰富的类匹配能力和方法匹配能力,可以更容易的指定自己期望的织入点。

Sermant的拦截器提供了Before、After、Throw三个关键的生命周期,并在其上提供了形如跳过方法执行,修改方法参数,修改方法返回,修改异常抛出等通用能力。

图- Sermant拦截器提供的能力

拦截器的Before逻辑将会被Sermant的切面调度器在方法执行前按照插件的加载顺序进行调度,这里我们可以通过Sermant提供的API来终止方法的执行,并且可以获取到当前拦截的对象的相关信息,并且还可以获取和修改方法的入参,这里就要注意了,修改入参可能会被其他插件所感知,这里就体现了切面调度器的重要性,如果修改参数产生了预期外的影响,可以通过调整插件顺序的方式来避免这种影响。

插件的After和Throw逻辑将会在目标方法执行结束时,被Sermant的切面调度器统一按照插件加载顺序的逆序进行调度,在此时我们还可以再次来修改方法的返回值和异常。在After中如果需要修改方法的返回值,则也同Before逻辑一样,需要注意拦截器的执行顺序,如果产生了预期外的影响,可以尝试通过调整插件顺序来进行避免。

在Throw逻辑中,只有当方法抛出异常时,Sermant才能触发拦截器处理Throw逻辑,如果异常在方法中被捕获,则无法触发Throw的拦截器处理逻辑,如果在Throw逻辑中将异常修改为null,此时方法将不再会抛出异常。

统一动态配置

《如何利用动态配置中心在JavaAgent中实现微服务的多样化治理》中,已经对动态配置进行了详细的介绍,本文就不再进行详细的叙述,Sermant动态配置模型是一种基于分层模型设计的配置管理方案,它的核心组件包括Group和Key。Sermant通过不同的Group(分组信息)来对配置项进行隔离,使配置管理更具灵活性和可扩展性;同时,通过Key对配置项进行具体属性的标识,实现了对配置项的精准控制和高效维护,其在主流的配置中心中的概念对应关系如下:

图- Sermant统一动态配置相关概念

Sermant为开发者和使用者屏蔽了配置中心的差异,可以无需修改任何代码,就可以让Sermant对接多种配置中心,开发者只需要通过Group和Key进行配置的划分,无需了解各配置中心的实际字段,就可以开发出不依赖配置中心的动态服务治理能力。

统一日志解析

日志是在程序开发中不可或缺的能力,通过日志可以快速找出程序运行时的状态及遇到的问题。Sermant的日志有两个很重要诉求,第一个就是需要隔离,避免Sermant日志系统对微服务带来不良的影响,例如破坏了微服务的日志配置,Sermant日志和微服务日志交叉输出,影响微服务日志检索定位问题。第二个就是需要有监控能力,可以高性能的将执行过程中的异常信息通过Sermant Backend可观测,及时发现边车运行的异常问题。

Sermant的统一日志,首先Sermant框架通过自定义的类加载器将日志引擎和微服务的日志引擎进行隔离,这样避免共用日志引擎,并且限制日志引擎的资源加载只在Sermant自定义的类加载器中进行:

@Override  
public Enumeration<URL> getResources(String name) throws IOException {  
    // 由于类隔离的原因针对StaticLoggerBinder不再通过父类加载器获取重复资源,只返回加载器内的资源  
    if ("org/slf4j/impl/StaticLoggerBinder.class".equals(name)) {  
        return findResources(name);  
    }  
    return super.getResources(name);  
}  

第二步Sermant框架通过自定义类加载器来限制日志引擎所能加载到的配置,通过限制"logback.xml"文件资源的加载,来限制日志的配置:

@Override  
public URL getResource(String name) {  
    URL url = null;  
  
    // 针对日志配置文件,定制化getResource方法,获取FrameworkClassloader下资源文件中的logback.xml  
    if (CommonConstant.LOG_SETTING_FILE_NAME.equals(name)) {  
        File logSettingFile = BootArgsIndexer.getLogSettingFile();  
        if (logSettingFile.exists() && logSettingFile.isFile()) {  
            try {  
                url = logSettingFile.toURI().toURL();  
            } catch (MalformedURLException e) {  
                url = findResource(name);  
            }  
        } else {  
            url = findResource(name);  
        }  
    }  
    if (url == null) {  
        url = super.getResource(name);  
    }  
    return url;  
}  

最后通过JUL桥接日志,借助于jul-to-slf4j (opens new window)的桥接能力将JUL日志桥接到日志引擎。最终,Sermant开发者在使用统一日志时,通过JUL接口来构造日志即可,无需再依赖其他第三方日志门面依赖,仅需使用Java 原生日志接口,日志和微服务完全隔离的,避免了边车日志系统对微服务日志系统带来不良的影响。

图- Sermant日志系统

除此之外,Sermant中改造了日志处理器,通过包装日志的桥接处理器,在高级别日志构造时通过Sermant的事件系统进行监控:

public class SermantBridgeHandler extends SLF4JBridgeHandler {  
    @Override  
    protected void callLocationAwareLogger(LocationAwareLogger lal, LogRecord record) {  
        // 覆写SLF4JBridgeHandler的日志转换方法,上报日志事件  
        int julLevelValue = record.getLevel().intValue();  
  
        if (julLevelValue > Level.INFO.intValue() && julLevelValue <= Level.WARNING.intValue()) {  
            // 记录警告级别日志  
            LogEventCollector.getInstance().offerWarning(record);  
        } else if (julLevelValue > Level.WARNING.intValue()) {  
            // 记录错误级别日志  
            LogEventCollector.getInstance().offerError(record);  
        }  
        super.callLocationAwareLogger(lal, record);  
    }  
}  

针对高级别的日志进行监控,可以通过配置事件系统将高级别日志进行异常的上报,通过Sermant Backend可以第一时间发现边车运行的异常状态。

结语

本文针对Sermant插件开发中的总会接触到的一些能力进行了更深层次的解析,基于更深入的了解,在插件开发时,才能更灵活的使用Sermant提供的丰富开发者能力,希望本篇文章可以对广大插件开发者带来一定的启发,除了上述能力,在插件开发中还可能需要用到利用Archetype能力快速构建项目并使用如心跳、链路标记等加速服务治理的开发,如何构建局部类加载环境等更多的开发指导可见Sermant开发者指南

开发完成后,如想在k8s环境下快速部署Sermant、动态的执行Sermant的安装和卸载、重复安装插件以及完成边车的自监控等,可通过Sermant用户使用手册学习更多技巧。

-----------------------------------------------------------------------------------------

Sermant作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。

点击关注,第一时间了解华为云新鲜技术~

 

标签:插件,拦截器,玩转,开发者,日志,Sermant,加载
From: https://www.cnblogs.com/huaweiyun/p/17913266.html

相关文章

  • 游刃有余:玩转Java泛型
    Java中的泛型提供了一种创建可以处理不同类型数据的可重用代码的方法。它允许用户定义可操作各种数据类型的类、接口和方法,而无需牺牲类型安全性。在Java5中引入的泛型已经成为Java编程语言的一个基本特性。在Java引入泛型之前,它使用原始类型来允许将各种类型的对象存储在......
  • 前端开发者
    当涉及到前端领域的成功人士,有许多杰出的开发者可以作为学习榜样。以下是一些在前端领域有所成就的人士,他们在社区中广受尊敬并有着丰富的经验和知识:DanAbramov:React的核心开发者之一,他的技术博客和社交媒体上的分享非常受欢迎。SarahDrasner:她是Vue.js的倡导者和贡献......
  • 这10个TypeScript高级技巧,助你成为更好的开发者!
    这10个TypeScript高级技巧,助你成为更好的开发者!前端学习站 ​关注他 在使用了一段时间的Typescript之后,我深深地感受到了Typescript在大中型项目中的必要性。可以提前避免很多编译期的bug,比如烦人的拼写问题。并且越来越多的包都在使用TS,所以学习它势在必行......
  • OpenAI 工程师自曝开发 ChatGPT 仅用时 8 天丨 RTE 开发者日报 Vol.108
     开发者朋友们大家好: 这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(RealTimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的**会议**」,但内容仅代表编辑......
  • MongoDB:Java开发者的新型数据库解决方案
    一、介绍MongoDB是一种高性能、开源的、面向文档的数据库系统,它使用C++语言编写,并提供了一系列强大的功能和特性。MongoDB具有灵活的数据模型、高效的查询性能、强大的扩展性和易用性等特点,使得它成为Java开发者的新型数据库解决方案。二、MongoDB的特点文档型数据模型:MongoDB采用......
  • 小白玩转博客园
    简介记录自己在使用博客园过程中遇到的各种问题,与解决方法。安装皮肤明确个人需求,是要炫酷色彩,还是要简约线条。响应式布局,支持手机阅读。加载要快,别半天还出不来内容。以下作者可供参考:guangzan,博客预览地址。GShang,博客园主题-Bili2.0。定制个人签名......
  • [.NET开发者的福音]一个方便易用的在线.NET代码编辑工具.NET Fiddle
    前言今天给大家分享一个方便易用的.NET在线代码编辑工具,能够帮助.NET开发人员快速完成代码编写、测试和分享的需求(.NET开发者的福音):.NETFiddle。.NETFiddle介绍我们可以不用再担心环境与庞大的IDE安装的问题,不管在任何时间,任何环境都可以在线运行调试!.NETFiddle是一个在......
  • MySQL锁:Java开发者必须掌握的关键技术
    一、介绍在多用户并发访问数据库时,为了保证数据的一致性和完整性,数据库系统需要使用锁来控制对共享资源的访问。MySQL作为一款流行的关系型数据库管理系统,也提供了丰富的锁机制来支持并发控制。对于Java开发者来说,了解和掌握MySQL锁是至关重要的,因为它可以帮助我们更好地设计和优化......
  • 玩转jvm
    1:什么是JVMJVM是Java虚拟机(JavaVirtualMachine)的缩写。它是Java编程语言的关键组成部分。JVM是一个运行在计算机上的虚拟机,它可以执行Java字节码(Javabytecode)程序。Java字节码是Java源代码经过编译后生成的中间代码,在JVM上可以被解释器实时地执行或者被编译成本地机器码执行......
  • 大模型那么火,教你一键ModelArts玩转开源LlaMA大模型
     本文分享自华为云社区《大模型那么火,教你一键Modelarts玩转开源LlaMA(羊驼)大模型》,作者:码上开花_Lancer。近日,LlaMA(羊驼)这个大模型再次冲上热搜!LLaMA(LargeLanguageModelMetaAI),由MetaAI发布的一个开放且高效的大型基础语言模型,共有7B、13B、33B、65B(650亿)四种版本。......