架构设计

JSF源码分析

JSF源码分析

1.7.4-HOTFIX-T4版本包布局及简要意义

JSF源码分析

看过了全包的简要,那么其间心的功用模块,就从常用的项目xml装备出发,便于咱们的理解。如下:

jsf-provider.xml装备

以咱们地址服务的jsf-provider.xml文件为例,即:

JSF源码分析

能够看到,在JSF的装备文件中,咱们并没有看到任何关于注册中心的内容。说到底,作为(集团自主研制的高效)RPC调用结构,其高可用的注册中心重中之重,所以带着这份疑问,持续往下探求,没有注册中心地址,这些标签是怎样完结服务的注册,订阅的。

装备解析

在Spring的系统中,Spring供给了可扩展Schema的支撑,即自界说的标签解析。

1、首要咱们发现装备文件中自界说的xsd文件,在标签名称上找到NamespaceUri链接jsf.jd.com/schema/jsf/…

2、然后依据SPI加载,在META-INF中找到界说好Spring.handlers文件和Spring.schemas文件,一个是详细的解析器的装备,一个是jsf.xsd的详细路径

Spring.handlers文件内容:
http\://jsf.jd.com/schema/jsf=com.jd.jsf.gd.config.spring.JSFNamespaceHandler
--------------------------------------------------------
Spring.schemas文件内容:
http\://jsf.jd.com/schema/jsf/jsf.xsd=META-INF/jsf.xsd

3、由此咱们进一步查询继承NameSpaceHanderSupport或许完成NameSpaceHandler对应的接口类,在咱们的jsf结构中JSFNamespaceHandler是采用继承前者(NameSpaceHanderSupport)去完成的,即:

com.jd.jsf.gd.config.spring.JSFNamespaceHandler

JSF源码分析

【补充】NamespaceHandler的功用便是解析咱们自界说的JSF命名空间的,为了便利起见,咱们完成NamespaceHandlerSupport,其内部经过BeanDefinitionParser对详细的标签进行处理,即对咱们界说的标签进行详细处理。

com.jd.jsf.gd.config.spring.JSFBeanDefinitionParser#parse

JSF源码分析

4、终究这些装备(也便是咱们在xml中装备的标签值)会解析成为ServerConfig和ProviderConfig,而且会依据所装备的特点,对相应的类进行特点的赋值。

com.jd.jsf.gd.config.ServerConfig

JSF源码分析

com.jd.jsf.gd.config.ProviderConfig

JSF源码分析

初始化

OK,咱们回到JSFNamespaceHandler看一下服务是怎么露出的。众所周知,在Spring容器中的bean一定会阅历一个初始化的进程。所以经过com.jd.jsf.gd.config.spring.JSFBeanDefinitionParser完成org.springframework.beans.factory.xml.BeanDefinitionParser来进行xml的解析,别的经过ParserContext中封装了BeanDefinitionRegistry目标,用于BeanDefinition的注册,用来初始化各个bean,即(com.jd.jsf.gd.config.spring.JSFBeanDefinitionParser#parse)。

如下:bean类ProviderBean会监听上下文事情,而且当整个容器初始化完毕之后会调用export()办法进行服务的露出。

com.jd.jsf.gd.config.spring.ProviderBean

JSF源码分析

服务露出

咱们回到源码中,发现其间心代码逻辑如下图:

com.jd.jsf.gd.config.ProviderConfig#doExport

JSF源码分析

com.jd.jsf.gd.config.ProviderConfig#doExport 该办法的全体逻辑如下:

1、首要进行各种根本的校验和阻拦,如:

  • [JSF-21200]provider的alias不能为空;

  • [JSF-21202]providerconfig的server为空;

  • [JSF-21203]同一接口+alias的provider装备了多个;

2、其次获取所有的RegistryConfig,假如获取不到注册的地址,那么就会走默许的注册中心地址:“i.jsf.jd.com”。

JSF源码分析

com.jd.jsf.gd.config.RegistryConfig

JSF源码分析

3、然后获取provider中装备的server,假如provider中存在server相关的装备,便是com.jd.jsf.gd.config.ServerConfig,此时会发动server(serverConfig.start()),而且采用默许对应的序列化方式(serverConfig.getSerialization(),默许msgpack)进行注册服务编码。

com.jd.jsf.gd.config.ServerConfig#start(服务发动)

JSF源码分析

4、start办法中会调用ServerFactory中的办法发生相对应的目标,然后会调用Server中的办法去发动Server,而在这个进程中,终究会对应到ServerTransportFactory发生相应的传输层,便是定位到JSFServerTransport的start()办法,在这里咱们能够看到,该部分完成了netty结构的transport层,在进入这个办法的时分,会依据是否装备运用epoll模型来选择所生成的目标是EpollServerSocketChannel或许NioServerSocketChannel,然后在ServerBootStrap中初始化相关的参数,直到终究绑定好端口号。

com.jd.jsf.gd.server.ServerFactory#initServer

JSF源码分析

com.jd.jsf.gd.transport.JSFServerTransport#start

JSF源码分析

5、终究经过(this.register();)服务注册而且露出出去,其间JsfRegistry这个类的目标,在该类的结构函数中会衔接jsf的注册中心,假如注册中心不可用的话,会生成并运用本地的文件而且开端看护线程,并运用两个线程池去发送心跳检测以及重试机制,别的一个线程池去检测衔接是否成功(com.jd.jsf.gd.registry.JSFRegistry#addRegistryStatListeners)。

com.jd.jsf.gd.registry.JSFRegistry

JSF源码分析

6、服务注册(com.jd.jsf.gd.registry.JSFRegistry#register)的进程会将对应的ProviderConfig转换为JsfUrl类,这里JsfUrl是整个结构的中心,他保存了一系列的装备,而且和其相同重要的还有订阅Url类SubscribeUrl,这里JsfUrl归于服务Url,服务Url中保存了协议,端口号,ip地址等相关重要的信息,而且回到上层JsfContext会将装备信息维护起来(JSFContext.cacheProviderConfig(this);)。到这里(this.exported = true;)provider的服务从装备装配到服务露出就完结了。

com.jd.jsf.gd.registry.JSFRegistry#register

JSF源码分析

com.jd.jsf.vo.JsfUrl

JSF源码分析

jsf-consumer.xml装备

以上是完结了provider服务的露出,那么咱们回到consumer中,看一下,如下:

JSF源码分析

咱们在上方的装备文件中发现到了注册中心地址i.jsf.jd.com,也便是说服务注册相关的装备没有写到jsf-provider.xml端,只是装备到了jsf-consumer.xml中罢了。

装备解析&初始化

装备解析进程是同上,就不多做赘述了,终究这些装备会解析成为ConsumerConfig和RegistryConfig,而且会依据所装备的特点,对相应的类进行特点的赋值。

com.jd.jsf.gd.config.ConsumerConfig->AbstractConsumerConfig

JSF源码分析

终究初始化映射到ConsumerBean类。

com.jd.jsf.gd.config.spring.ConsumerBean

JSF源码分析

服务订阅

不过咱们发现其完成了FactoryBean,如咱们所了解的,假如一个 bean 完成了该接口(FactoryBean),它被用作一个目标的工厂来露出,而不是直接作为一个将自己露出的 bean 实例。这也就意味着需求调用getObject()来获取真正的实例化目标(可能是同享的或独立的)。之所以这样运用的原因在于咱们的Consumer端只能调用接口,接口是无法直接运用的,它需求被动态署理封装,发生署理目标,再把它放入Spring容器中。因此运用FactoryBean其实只是为了便利创立署理目标罢了。

在getObject()办法中ConsumerBean会调用子类consumerConfig的refer()办法,然后开端了客户端的初始化的进程。在refer()进程中,consumer会去订阅相关的provider的服务。中心代码如下:

JSF源码分析

com.jd.jsf.gd.config.ConsumerConfig#refer

该办法的全体逻辑为,如下:

1、首要进行各种根本的校验和阻拦,如:

  • consumer的alias不能为空,请查看装备

  • 同一个接口+alias+protocol 本地装备的超越三次,抛出发动反常

2、一些不同装备的逻辑,如是否泛化调用,是否走injvm调用等

3、经过工厂方式生成一个Client的实例,因为上面ConsumerConfig->AbstractConsumerConfig默许的集群战略failover,所以在没有装备的情况下会生成FailoverClient,然后进行相关的Invoke操作(this.proxyInvoker = new ClientProxyInvoker(this);)。

com.jd.jsf.gd.client.ClientFactory#getClient

JSF源码分析

【补充】目前JSF支撑的集群战略有,failover:失利重试(默许);failfast:失利疏忽;pinpoint:定点调用;

4、在client中,先界说其负载均衡,然后判别是否需延迟建立长链接。否的话,会直接进行初始化衔接。其次假如未界说路由规则,或许不存在衔接时,Client会先初始化相关的路由以及初始化衔接,如下:

JSF源码分析

5、在初始化衔接(initConnections)中会进行调用ConsumerConfig中的subscribe()进行服务的订阅,而且在初始化的进程中,consumer会衔接相应的Providers。

com.jd.jsf.gd.client.Client#initConnections

JSF源码分析

6、咱们看到在获取衔接进程(connectToProviders())中,假如衔接池中现已存在则直接返回,不存在则需求从头建立衔接。如下:会初始化一个名为JSF-CLI-CONN-#interfaceId的线程池,在线程池中会履行对应使命,即获取到一个ClientTransport目标。

com.jd.jsf.gd.client.Client#connectToProviders

JSF源码分析

假如衔接(com.jd.jsf.gd.transport.ClientTransportFactory#initTransport),则:1)首要判别相关协议;

com.jd.jsf.gd.transport.ClientTransportFactory#initTransport

JSF源码分析

2)然后BuildChannel建立衔接(com.jd.jsf.gd.transport.ClientTransportFactory#BuildChannel),在这里会对应到Server端发动代码的相关参数,便是该处会监听服务器端的端口号,绑定到相应的ip,并依据对应的通道做数据传输。

com.jd.jsf.gd.transport.ClientTransportFactory#BuildChannel

JSF源码分析

3)而且,对于transport层,咱们应该重视他注入了哪些pipeline:能够看到JSFEncoder/JSFDecoder用来解码,编码详细的协议。handler则是详细的channel处理器,用于处理心跳包,客户端请求,和音讯路由。终究回到最上层(com.jd.jsf.gd.config.ConsumerConfig#refer)将装备文件维护起来(JSFContext.cacheConsumerConfig(this);)。

com.jd.jsf.gd.transport.ClientChannelInitializer#initChannel

JSF源码分析

综上简略的过了一遍各自功用的初始化加载方式等简略论述了一下,不过阅览起来肯定是偏零散的,需求咱们依据以上进程和源码包进行进一步探索。别的咱们也能够用服务器发动日志上,看到JSF中心流程的日志记录,部分截图如下(感兴趣能够在预发机器的发动日志中翻阅):

服务器发动日志(JSF)

JSF源码分析

JSF源码分析

总的来说,其供给者Provider,消费者Consumer,注册Registry等关系应该如下图所示:

Architecture

JSF源码分析

榜首部分:Provider端发动服务向注册中心Session注册自己的服务,注册服务的方式如:[jsf://192.168.124.73:22000/?safVersion=210&jsfVersion=1691&interface=com.jd.wxt.material.service.DspTaskQueueService&alias=chengzhi36_42459] 此时在注册服务,露出服务的进程中,此时JsfRegistry会进行初始化衔接。在此进程中,假如有装备对应的Server端,那么还会对Server进行初始化.

第二部分:Consumer在榜首次获取实体信息时,因为其是FactoryBean,故有必要调用getObject()办法去获取实体,在此进程会调ConsumerConfig的refer()办法,即可将Client端注册到jsf注册中心并订阅对应alias下Provider的改变。在这个进程中会初始化Consumer以及Provider端的监听器,对相关的Consumer和Provider做事情监听。此进程会发生Client目标,而且在初始化该目标时,会默许的去运用随机的负载均衡战略,而且会初始化路由,初始化路由后,会调用ConsumerConfig的subscribe办法,然后会初始化客户端对之前发动的Server进行衔接。

第三部分:注册中心会异步告诉Consumer是否需求从头订阅Provider。

第四部分:便是直接调用办法invoke。

第五部分:Monitor对服务进行监控,管理或许降级容灾。

流程图

JSF源码分析

终究调用,如下:

com.jd.jsf.gd.filter.FilterChain#invoke

ResponseMessage response = this.filterChain.invoke(requestMessage);

JSF源码分析

参考资料

SPI: jnews.jd.com/circle-info…

NamespaceHandler:www.yisu.com/zixun/44786…

jsf:cf.jd.com/pages/viewp…

netty:www.cnblogs.com/jing99/p/12…


本次就先写到这,文章中如有问题,欢迎留言斧正。也希望能和更多情投意合的伙伴沟通交流。后续会再更新关于JSF心跳检测、服务管理、服务反注册、钩子工具等模块细化的剖析以及当时咱们渠道系统初步接入wormhole渠道(中间件mesh化)的一些经验共享。欢迎大家点赞重视。

本文正在参与「金石计划」