玩转Sermant开发,开发者能力机制解析
本文分享自华为云社区《开发者能力机制解析,玩转Sermant开发》,作者:华为云开源 。
前言:
在《Sermant框架下的服务治理插件快速开发及使用指南》中带大家一起体验了Sermant插件的开发,快速的了解了Sermant插件开发的全过程,本着从入门到精通的思路,本文对在开发中所常用的能力,从机制上进行更深入的解析。
插件加载&插件调度
解析插件的加载和调度前,可以再回顾一下,Sermant作为一个基于Java字节码增强技术的插件化服务网格,在设计之初就为插件设计了完整的类隔离机制,在《Sermant类隔离架构解析——解决JavaAgent场景类冲突的实践》中进行的详尽的介绍和分析,避免让开发者陷入到复杂的类冲突问题中,从开发者视角来看,可以无需关注类冲突问题,也对Sermant的类隔离机制无感知,同时借助Sermant的局部类加载机制,可以更建议的开发出高性能的服务治理插件。
图- 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官网:
- GitHub仓库地址:
玩转Sermant开发,开发者能力机制解析的更多相关文章
- 用RegularJS开发小程序 — mpregular解析
本文来自网易云社区. Mpregular 是基于 RegularJS(简称 Regular) 的小程序开发框架.开发者可以将直接用 RegularJS 开发小程序,或者将现有的 RegularJS 应 ...
- 知名互联网公司校招 Java 开发岗面试知识点解析
天之道,损有余而补不足,是故虚胜实,不足胜有余. 本文作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向.在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几 ...
- 转 Java Classloader机制解析
转 Java Classloader机制解析 发表于11个月前(2014-05-09 11:36) 阅读(693) | 评论(0) 9人收藏此文章, 我要收藏 赞1 慕课网,程序员升职加薪神器,点 ...
- 【FastDev4Android框架开发】RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout(三十一)
转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/49992269 本文出自:[江清清的博客] (一).前言: [好消息] ...
- CDN 的缓存与回源机制解析
CDN的缓存与回源机制解析 CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器.这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户 ...
- iOS开发中的Html解析方法
iOS开发中的Html解析方法 本文作者为大家介绍了在iOS开发中的Html解析方法,并同时提供了Demo代码的下载链接,Demo 解析了某个网站(具体可在代码中查看)的html网页,提取了图片以及标 ...
- Java并发编程:Concurrent锁机制解析
Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...
- Windows内核开发-6-内核机制 Kernel Mechanisms
Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...
- Android开发:碎片Fragment完全解析fragment_main.xml/activity_main.xml
Android开发:碎片Fragment完全解析 为了让界面可以在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能,它非常类似于Activity,可以像 Activi ...
- 8天玩转并行开发——第三天 plinq的使用
原文 8天玩转并行开发——第三天 plinq的使用 相信在.net平台下,我们都玩过linq,是的,linq让我们的程序简洁优美,简直玩的是爱不释手,但是传统的linq只是串行代码,在并行的 年代如果 ...
随机推荐
- WPF性能优化:Freezable 对象
Freezable是WPF中一个特殊的基类,用于创建可以冻结(Freeze)的可变对象.冻结一个对象意味着将其状态设置为只读,从而提高性能并允许在多线程环境中共享对象. Freezable的应用 我们 ...
- nginx学习(基本概念、配置和命令、反向代理、负载均衡、动静分离)
之前都只会照着网上的nginx配置和代码什么的直接拿过来用,但是没系统学习过,所以来系统学习一下nginx内容. 建议服务器不要关闭防火墙,按需开启端口就好,然后云服务器也要设置SSH密钥,安全性高一 ...
- CSS色域、色彩空间、CSS Color 4新标准
引言 近期,三大主流浏览器引擎均发布最新版本,支持W3C的CSS Color 4标准,包含新的取色方法color()和相应语法,可展示更多的色域及色彩空间,这意味着web端能展示更丰富更高清的色彩.虽 ...
- UML类图(最重要的三个关系)
关联关系 ============= 关联关系:B是A的属性(A contains B),则A-->B:另外的: 都是特殊的关联关系 AB: 聚合(Aggregation)关系表示整体与部分的关 ...
- EhCache使用详细介绍
http://hi.baidu.com/yjl_zzh/item/18e6518397cdd1d9d1f8cdfb 2.EhCache的使用注意点 当用Hibernate的方式修改表数据(sav ...
- .Net Core 3.1升级 .Net 5后出现代码错误 rzc generate exited with code 1.
安装.Net 5后出现错误,错误定位到了CodeGeneration相关的文件,找了半天也不知道哪里的问题. 升级类库,清理解决方案,删除obj.bin文件夹什么的卵用没有. 最后发现升级.Net 5 ...
- shopify本地开发 新的地方
看了一些教程有些旧地方通不过,自己摸索下 1.创建app 点击Settings,再点击Apps and sales channels 点Allow custom app development 继续 ...
- 确定性有限状态自动机 DFA
前言 在计算理论中,确定有限状态自动机或确定有限自动机(英语:deterministic finite automaton, DFA)是一个能实现状态转移的自动机.对于一个给定的属于该自动机的状态和一 ...
- JS toFixed()方法精度丢失解决方法
JS toFixed()方法精度丢失 toFixed()方法可把Number四舍五入为指定小数位数的数字.但这个方法并不完全遵从四舍五入的规则,如 2.485.toFixed(2) //=>2. ...
- StackGres 1.6 数据库平台工程功能介绍以及快速上手
StackGres 1.6 数据库平台工程功能 声明式 K8S CRs StackGres operator 完全由 Kubernetes 自定义资源管理.除了 kubectl 或任何其他 Kuber ...