代理ip拿货
:系统的复杂性给与运维及故障处理带来更大的挑战,如何快速处理故障解决线上问题,这是考验一个企业基础设施建设的重要关卡。
最初,开发者使用SDK来解决这些问题,通过在代码中集成各种库和工具来实现服务治理。然而,随着微服务架构的规模不断扩大,这种方法逐渐显现出局限性:
为了解决这些问题,服务网格(Service Mesh)应运而生。服务网格通过在服务间引入一个代理层(通常称为Sidecar),将服务治理的逻辑从应用代码中分离出来,实现了更好的治理和管理。然而,服务网格的引入也带来了额外的复杂性和性能开销。
在这样的背景下,我们通过Java字节码增强技术,在服务治理领域提供了一种创新的解决方案。它结合了SDK和服务网格的优点,提供了无侵入性的治理逻辑注入、灵活的扩展性和高效的性能优化。这种方法不仅简化了微服务架构中的服务治理,还提升了系统的可观测性和安全性,对于现代微服务环境具有重要意义。
Joylive Agent 是一个基于字节码增强的框架,专注于多活和单元化场景下的流量治理。它提供了以下功能:多活流量调度、全链路灰度发布、QPS和并发限制、标签路由、负载均衡,熔断降级,鉴权等流量治理策略。其特性包括微内核架构、强类隔离、业务零侵入等,使其在保持高性能的同时对业务代码影响最小,是面向Java领域的新一代Proxyless Service Mesh探索实现。
最初,大多数应用都是作为单体应用开发的。所有功能都集中在一个代码库中,部署也是作为一个整体。这种形式也是我们学习编程之初,最原始的模样。确切的说,这种形态并不属于微服务。如下图所示:
随着应用规模的成长,此时会考虑将每个功能模块(服务)拆分为独立的应用,也就是垂直拆分,拥有自己的代码库、数据库和部署生命周期。服务之间通过轻量级协议(如HTTP、gRPC)通信。也就是正式开启了面向服务的架构(SOA)。这种形态体现为:服务发现通过DNS解析,Consumer与Provider之间会有LB进行流量治理。服务间通过API进行通信。如下图所示:
缺点: 增加了分布式系统的复杂性,需要处理服务间通信、数据一致性、服务发现、负载均衡等问题,也因为中间引入LB而降低了性能。
这个阶段引入更多的微服务治理和管理工具,使用专业的微服务框架或中间件,通过专门定制的微服务通讯协议,让应用取得更高的吞吐性能。如API网关、注册中心、分布式追踪等。DevOps和持续集成/持续部署(CI/CD)流程成熟。代表产物如Spring Cloud,Dubbo等。此时典型的微服务场景还都是具体的微服务SDK提供的治理能力。通讯流程为:SDK负责向注册中心注册当前服务信息,当需要进行服务消费时,同样向注册中心请求服务提供者信息,然后直连服务提供者IP及端口并发送请求。如下图所示:
缺点: 系统复杂性和运维成本较高,需要成熟的技术栈和团队能力。微服务治理能力依赖SDK,升级更新成本高,需要绑定业务应用更新。
随着云原生容器化时代的到来,服务网格是一种专门用于管理微服务之间通信的基础设施层。它通常包含一组轻量级的网络代理(通常称为 sidecar),这些代理与每个服务实例一起部署,这利用了K8s中Pod的基础能力。服务网格负责处理服务间的通信、流量管理、安全性、监控和弹性等功能。这种微服务治理方式也可以称之为Proxy模式,其中SideCar即作为服务之间的Proxy。如下图所示:
优点: 主要优点是解耦业务逻辑与服务治理的能力,通过集中控制平面(control plane)简化了运维管理。
有没有一种微服务治理方案,既要有SDK架构的高性能、多功能的好处,又要有边车架构的零侵入优势, 还要方便好用?这就是项目设计的初衷。项目的设计充分考虑了上面微服务的架构历史,结合多活流量治理模型,进行了重新设计。其中项目设计到的主要技术点如下,并进行详细解析。如下图所示:
Proxyless模式(无代理模式)是为了优化性能和减少资源消耗而引入的。传统的微服务网格通常使用边车代理(Sidecar Proxy)来处理服务之间的通信、安全、流量管理等功能。
我们选择通过Java Agent模式实现Proxyless模式是一种将服务网格的功能(如服务发现、负载均衡、流量管理和安全性)直接集成到Java应用程序中的方法。这种方式可以利用Java Agent在运行时对应用程序进行字节码操作,从而无缝地将服务网格功能注入到应用程序中,而无需显式修改应用代码。Java Agent模式实现Proxyless的优点如下:
减少网络延迟:传统的边车代理模式会引入额外的网络跳数,因为每个请求都需要通过边车代理进行处理。通过Java Agent直接将服务网格功能注入到应用程序中,可以减少这些额外的网络开销,从而降低延迟。
降低资源消耗:不再需要运行额外的边车代理,从而减少了CPU、内存和网络资源的占用。这对需要高效利用资源的应用非常重要。
统一管理:通过Java Agent实现Proxyless模式,所有服务网格相关的配置和管理可以集中在控制平面进行代理ip拿货,而无需在每个服务实例中单独配置边车代理。这简化了运维工作,特别是在大型分布式系统中。
减少环境复杂性:通过消除边车代理的配置和部署,环境的复杂性降低,减少了可能出现的配置错误或版本不兼容问题。
数据局面升级:Java Agent作为服务治理数据面,天然与应用程序解耦,这点是相对于SDK的最大优点。当数据面面临版本升级迭代时,可以统一管控而不依赖于用户应用的重新打包构建。
无需修改源代码与现有生态系统兼容:Java Agent可以在运行时对应用程序进行字节码操作,直接在字节码层面插入服务网格相关的逻辑,而无需开发者修改应用程序的源代码。这使得现有应用能够轻松集成Proxyless模式。
动态加载和卸载:Java Agent可以在应用程序启动时或运行时动态加载和卸载。这意味着服务网格功能可以灵活地添加或移除,适应不同的运行时需求。
支持遗留系统:对于无法修改源代码的遗留系统,Java Agent是一种理想的方式,能够将现代化的服务网格功能集成到老旧系统中,提升其功能和性能。
通过Java Agent实现Proxyless模式,能够在保持现有系统稳定性的同时,享受服务网格带来的强大功能,是一种高效且灵活的解决方案。
微内核架构是一种软件设计模式,主要分为核心功能(微内核)和一系列的插件或服务模块。微内核负责处理系统的基础功能,而其他功能则通过独立的插件或模块实现。这种架构的主要优点是模块化、可扩展性强,并且系统的核心部分保持轻量级。
核心组件:框架的核心组件更多的定义核心功能接口的抽象设计,模型的定义以及agent加载与类隔离等核心功能,为达到最小化依赖,很多核心功能都是基于自研代码实现。具体可参见joylive-core代码模块。
插件化设计:使用了模块化和插件化的设计,分别抽象了像保护插件,注册插件,路由插件,透传插件等丰富的插件生态,极大的丰富了框架的可扩展性,为适配多样化的开源生态奠定了基础。具体可参见joylive-plugin代码模块。
实现扩展接口,并使用@Extension注解来进行扩展实现的声明。如下是实现了LoadBalancer接口的实现类:
说到依赖注入估计大家会立马想到Spring,的确这是Spring的看家本领。在复杂的工程中,自动化的依赖注入确实会简化工程的实现复杂度。让开发人员从复杂的依赖构建中脱离出来,专注于功能点设计开发。依赖注入的实现是基于上面插件扩展体系,是插件扩展的功能增强。并且依赖注入支持了两类场景:注入对象与注入配置。该功能主要有4个注解类与3个接口构成。
这是一个非常简洁的可以应用于类、接口(包括注解类型)或枚举注解。其目的为了标识哪些类开启了自动注入对象的要求。这点不同于Spring的控制范围,而是按需注入。实例构建完成后,在自动注入的逻辑过程中会针对添加@Injectable注解的实例进行依赖对象注入。
该注解用于自动注入值到字段。它用于指定一个字段在运行时应该注入一个值,支持基于配置的注入。还可以指示被注入的值是否可以为 null,如果注入过程中无注入实例或注入实例为null,而nullable配置为false,则会抛出异常。loader定义了指定要为注释字段加载的资源或类实现的类型。ResourcerType为枚举类型,分别是:CORE,CORE_IMPL,PLUGIN。划分依据是工程打包后的jar文件分布目录。因为不同目录的类加载器是不同的(类隔离的原因,后面会讲到)。所以可以简单理解,这个配置是用于指定加载该对象所对应的类加载器。
这个注解类似于@Injectable,用于指定哪些类启用自动注入配置文件的支持。prefix指定用于配置键的前缀。这通常意味着前缀将来自类名或基于某种约定。auto指示配置值是否应自动注入到注解类的所有合规字段中。默认值为 false,这意味着默认情况下未启用自动注入。
该注解用于指定字段配置详细信息。它定义了字段的配置键以及配置是否为可选。此注释可用于在运行时自动将配置值加载到字段中,并支持指定缺少配置(无配置)是否被允许。nullable指示字段的配置是否是可选的。如果为真,则系统将允许配置缺失而不会导致错误。
Java 的字节码增强(Bytecode Enhancement)是一种动态改变 Java 字节码的技术,允许开发者在类加载之前或加载过程中修改类的字节码。这种机制在 AOP(面向切面编程)、框架增强、性能监控、日志记录等领域广泛应用。目前看此技术在APM产品使用较多,而针对流量治理方向的开源实现还是比较少的。
进行字节码增强的框架有很多,例如:JavaAssist、ASM、ByteBuddy、ByteKit等。我们针对字节码增强的过程及重要对象进行了接口抽象,并以插件化方式适配了ByteBuddy,开发了一种默认实现。当然你也可以使用其他的框架实现相应的接口,作为扩展的其他实现方式。下面以ByteBuddy为例,展示一个入门实例:
这个例子展示了如何使用ByteBuddy来增强SimpleClass的sayHello方法。让我解释一下这个过程:
我们创建了一个SimpleInterceptor类,包含了我们想要在原方法执行前后添加的逻辑。
当然,工程级别的实现是远比上面Demo的组织形式复杂的。插件是基于扩展实现的,有多个扩展组成,对某个框架进行特定增强,实现了多活流量治理等等业务逻辑。一个插件打包成一个目录,如下图所示:
该dubbo插件,支持了3个版本,增强了注册中心,路由和链路透传的能力。下面介绍一下在joylive-agent中如何实现一个字节码增强插件。
增强插件(功能实现层面的插件)接口的定义采用了插件(系统架构层面的插件)扩展机制。如下代码所示,定义的增强插件名称为PluginDefinition。
接口共定义了两个方法:getMatcher用于获取匹配要增强类的匹配器,getInterceptors是返回要增强目标类的拦截器定义对象。
拦截器定义接口主要是用来确定拦截增强位置(也就是方法),定位到具体方法也就找到了具体增强逻辑执行的位置。拦截器定义接口并没有采用扩展机制,这是因为具体到某个增强目标类后,要增强的方法与增强逻辑已经是确定行为,不再需要通过扩展机制实例化对象,在具体的增强插件定义接口实现里会直接通过new的方式构造兰机器定义实现。拦截器定义接口如下:
该接口同样抽象了两个方法:getMatcher用于获取匹配要增强方法的匹配器,getInterceptor是用于返回具体的增强逻辑实现,我们称之为拦截器(Interceptor)。
拦截器的实现类是具体增强逻辑的载体,当我们要增强某个类的某个方法时,与AOP机制同理,我们抽象了几处拦截位置。分别是:方法执行前(刚进入方法执行逻辑);方法执行结束时;方法执行成功时(无异常);方法执行出错时(有异常)。接口定义如下:
增强逻辑的实现可以针对不同的功能目标,选择合适的增强点。同样,拦截点的入参ExecutableContext也是非常重要的组成部分,它承载了运行时的上下文信息,并且针对不同的增强目标我们做了不同的实现。如下图所示:
类加载的原理比较容易理解,因为在Java Agent模式下,应用启动时agent需要加载它所依赖的jar包。然而,如果这些类在加载后被用户应用的类加载器感知到,就可能导致类冲突甚至不兼容的风险。因此,引入类隔离机制是为了解决这个问题。类隔离的实现原理并不复杂。首先,需要实现自定义的类加载器;其次,需要打破默认的双亲委派机制。通过这两步,类隔离可以实现多层次的隔离,从而避免类冲突和不兼容问题。如下图所示:
整个框架的核心行为就是治理请求,而站在一个应用的视角我们可以大体把请求抽象为两类:InboundRequest与OutboundRequest。InboundRequest是外部进入当前应用的请求,OutboundRequest是当前应用发往外部资源的请求。同样的处理这些请求的过滤器也同样分为InboundFilter与OutboundFilter。
如上图所示,展现了适配Dubbo的请求对象的DubboOutboundRequest与DubboInboundRequest,体现了一个OutboundRequest与InboundRequest的实现与继承关系。整体看起来确实比较复杂。这是因为在请求抽象时,不仅要考虑请求是Inbound还是Outbound,还要适配不同的协议框架。例如,像Dubbo和JSF这样的私有通讯协议框架需要统一为RpcRequest接口的实现,而SpringCloud这样的HTTP通讯协议则统一为HttpRequest。再加上Inbound和Outbound的分类维度,整体的抽象在追求高扩展性的同时也增加了复杂性。
下面提供的流量治理功能,以API网关作为东西向流量第一入口进行流量识别染色。在很大程度上,API网关作为东西向流量识别的第一入口发挥了重要作用。API网关在接收到南北向流量后,后续将全部基于东西向流量治理。
应用多活通常包括同城多活和异地多活,异地多活可采用单元化技术来实现。下面描述整个多活模型涉及到的概念及嵌套关系。具体实现原理如下图所示:
在模型和权限设计方面,我们支持多租户模式。一个租户可以有多个多活空间,多活空间构成如下所示:
单元是逻辑上的概念,一般对应一个地域。常用于异地多活场景,通过用户维度拆分业务和数据,每个单元独立运作,降低单元故障对整体业务的影响。
单元的属性包括单元代码、名称、类型(中心单元或普通单元)、读写权限、标签(如地域和可用区)、以及单元下的分区。
路由变量是决定流量路由到哪个单元的依据,通常是用户账号。每个变量可以通过不同的取值方式(如Cookie、请求头等)获取,且可以定义转换函数来获取实际用户标识。
单元规则定义了单元和分区之间的流量调度规则。根据路由变量计算出的值,通过取模判断流量应路由到哪个单元。它的属性包括多活类型、变量、变量取值方式、计算函数、变量缺失时的操作、以及具体的单元路由规则。
多活域名描述启用多活的域名,用于在网关层进行流量拦截和路由。支持跨地域和同城多活的流量管理,配置路径规则以匹配请求路径并执行相应的路由规则。
泳道是一种隔离和划分系统中不同服务或组件的方式,类似于游泳池中的泳道划分,确保每个服务或组件在自己的“泳道”中独立运作。泳道概念主要用于以下几种场景:
在多租户系统中,泳道通常用于隔离不同租户的资源和服务。每个租户有自己的独立“泳道”,以确保数据和流量的隔离,防止不同租户之间的相互影响。
泳道可以用于根据特定规则(例如用户属性、地理位置、业务特性等)将流量分配到不同的微服务实例或集群中。这种方式允许团队在某些条件下测试新版本、进行蓝绿部署、金丝雀发布等,而不会影响到其他泳道中的流量。
在某些场景下,泳道也可以代表业务逻辑的划分。例如,一个电商平台可能会针对不同的用户群体(如普通用户、VIP用户)提供不同的服务路径和处理逻辑,形成不同的泳道。
泳道可以用来管理微服务的不同版本,使得新版本和旧版本可以在不同的泳道中并行运行,从而降低升级时的风险。
在开发和测试过程中,不同的泳道可以用于隔离开发中的新功能、测试环境、甚至是不同开发团队的工作,从而减少互相干扰。
泳道的核心目的是通过隔离服务或资源,提供独立性和灵活性,确保系统的稳定性和可扩展性。这种隔离机制帮助组织更好地管理复杂系统中的多样性,尤其是在处理高并发、多租户、或者需要快速迭代的场景下。功能流程如下图所示:
微服务治理策略是指在微服务架构中,为确保服务的稳定性、可靠性、安全性以及高效运作而制定的一系列管理和控制措施。这些策略帮助企业有效地管理、监控、协调和优化成百上千个微服务的运行,以应对分布式系统的复杂性。目前我们实现了部分主流的微服务策略例如:负载均衡,重试,限流,熔断降级,标签路由,访问鉴权等,更多的实用策略也在陆续补充中。微服务框架方面已经支持主流框架,如:Spring Cloud,Dubbo2/3,JSF,SofaRpc等。
服务治理策略放在分组、路径和方法上,可以逐级设置,下级默认继承上级的配置。服务的默认策略设置到默认分组default上。
在应用启动过程中,注册插件会拦截获取到消费者和服务提供者的初始化方法,会修改其元数据,增加多活和泳道的标签。后续往注册中心注册的时候就会带有相关的标签了。这是框架所有治理功能的前提基础。以下是Dubbo服务提供者注册样例:
如果注意ServiceConfigInterceptor的增强会发现,在给注册示例打标之后,还有一部分逻辑,如下:
policySupplier.subscribe所执行的是策略订阅逻辑。因为策略是支持热更新并实时生效的,策略订阅逻辑便是开启了订阅当前服务在控制台所配置策略的逻辑。
入流量拦截也就是拦截Inbound请求,拦截相关框架的入流量处理链的入口或靠前的处理器的相关逻辑并予以增强。下面以Dubbo3的拦截点为例。
出流量拦截也就是拦截Outbound请求,拦截相关框架的出流量处理链的入口并予以增强。下面以Dubbo2的拦截点为例。
如果只开启了多活或泳道治理,则只需对后端实例进行过滤,可以拦截负载均衡或服务实例提供者相关方法
同样,出流量拦截也是采用了责任链模式设计了OutboundFilterChain,用户可以根据自己的需求扩展实现OutboundFilter,目前针对已支持功能内置了部分实现,如下所示:
我们有更高的目标和方向,希望更多有志于打造开源优品的朋友加入进来,一起为开源事业贡献自己的光与热。
下一篇:KubeCon China 2024全球大会在香港举行,京东云受邀参加探讨云原生、开源及 AI