作者:京东批发 李孟冬
架构设计
1.7.4-HOTFIX-T4 版本包布局及简要含意
看过了全包的简要,那么其外围的功能模块,就从罕用的我的项目 xml 配置登程,便于咱们的了解。如下:
jsf-provider.xml 配置
以咱们地址服务的 jsf-provider.xml 文件为例,即:
能够看到,在 JSF 的配置文件中,咱们并没有看到任何对于注册核心的内容。说到底,作为(团体自主研发的高效)RPC 调用框架,其高可用的注册核心重中之重,所以带着这份纳闷,持续往下探索,没有注册核心地址,这些标签是怎么实现服务的注册,订阅的。
配置解析
在 Spring 的体系中,Spring 提供了可扩大 Schema 的反对,即自定义的标签解析。
1、首先咱们发现配置文件中自定义的 xsd 文件,在标签名称上找到 NamespaceUri 链接 http://jsf.jd.com/schema/jsf/jsf.xsd
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
【补充】NamespaceHandler 的性能就是解析咱们自定义的 JSF 命名空间的,为了不便起见,咱们实现 NamespaceHandlerSupport,其外部通过 BeanDefinitionParser 对具体的标签进行解决,即对咱们定义的标签进行具体解决。
com.jd.jsf.gd.config.spring.JSFBeanDefinitionParser#parse
4、最终这些配置(也就是咱们在 xml 中配置的标签值)会解析成为 ServerConfig 和 ProviderConfig,并且会根据所配置的属性,对相应的类进行属性的赋值。
com.jd.jsf.gd.config.ServerConfig
com.jd.jsf.gd.config.ProviderConfig
初始化
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
服务裸露
咱们回到源码中,发现其外围代码逻辑如下图:
com.jd.jsf.gd.config.ProviderConfig#doExport
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”。
com.jd.jsf.gd.config.RegistryConfig
3、而后获取 provider 中配置的 server,如果 provider 中存在 server 相干的配置,即是 com.jd.jsf.gd.config.ServerConfig,此时会启动 server(serverConfig.start()),并且采纳默认对应的序列化形式(serverConfig.getSerialization(),默认 msgpack)进行注册服务编码。
com.jd.jsf.gd.config.ServerConfig#start(服务启动)
4、start 办法中会调用 ServerFactory 中的办法产生绝对应的对象,而后会调用 Server 中的办法去启动 Server,而在这个过程中,最终会对应到 ServerTransportFactory 产生相应的传输层,即是定位到 JSFServerTransport 的 start()办法,在这里咱们能够看到,该局部实现了 netty 框架的 transport 层,在进入这个办法的时候,会依据是否配置应用 epoll 模型来抉择所生成的对象是 EpollServerSocketChannel 或者 NioServerSocketChannel,而后在 ServerBootStrap 中初始化相干的参数,直到最初绑定好端口号。
com.jd.jsf.gd.server.ServerFactory#initServer
com.jd.jsf.gd.transport.JSFServerTransport#start
5、最初通过(this.register();)服务注册并且裸露进来,其中 JsfRegistry 这个类的对象,在该类的构造函数中会连贯 jsf 的注册核心,如果注册核心不可用的话,会生成并应用本地的文件并且开始守护线程,并应用两个线程池去发送心跳检测以及重试机制,另外一个线程池去检测连贯是否胜利(com.jd.jsf.gd.registry.JSFRegistry#addRegistryStatListeners)。
com.jd.jsf.gd.registry.JSFRegistry
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
com.jd.jsf.vo.JsfUrl
jsf-consumer.xml 配置
以上是实现了 provider 服务的裸露,那么咱们回到 consumer 中,看一下,如下:
咱们在上方的配置文件中发现到了注册核心地址 i.jsf.jd.com,也就是说服务注册相干的配置没有写到 jsf-provider.xml 端,只是配置到了 jsf-consumer.xml 中而已。
配置解析 & 初始化
配置解析过程是同上,就不多做赘述了,最终这些配置会解析成为 ConsumerConfig 和 RegistryConfig,并且会根据所配置的属性,对相应的类进行属性的赋值。
com.jd.jsf.gd.config.ConsumerConfig->AbstractConsumerConfig
最终初始化映射到 ConsumerBean 类。
com.jd.jsf.gd.config.spring.ConsumerBean
服务订阅
不过咱们发现其实现了 FactoryBean,如咱们所理解的,如果一个 bean 实现了该接口(FactoryBean),它被用作一个对象的工厂来裸露,而不是间接作为一个将本人裸露的 bean 实例。这也就意味着须要调用 getObject()来获取真正的实例化对象(可能是共享的或独立的)。之所以这样应用的起因在于咱们的 Consumer 端只能调用接口,接口是无奈间接应用的,它须要被动静代理封装,产生代理对象,再把它放入 Spring 容器中。因而应用 FactoryBean 其实只是为了不便创立代理对象而已。
在 getObject()办法中 ConsumerBean 会调用子类 consumerConfig 的 refer()办法,从而开始了客户端的初始化的过程。在 refer()过程中,consumer 会去订阅相干的 provider 的服务。外围代码如下:
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 反对的集群策略有,failover:失败重试(默认);failfast:失败疏忽;pinpoint:定点调用;
4、在 client 中,先定义其负载平衡,而后判断是否需提早建设长链接。否的话,会间接进行初始化连贯。其次如果未定义路由规定,或者不存在连贯时,Client 会先初始化相干的路由以及初始化连贯,如下:
5、在初始化连贯(initConnections)中会进行调用 ConsumerConfig 中的 subscribe()进行服务的订阅,并且在初始化的过程中,consumer 会连贯相应的 Providers。
com.jd.jsf.gd.client.Client#initConnections
6、咱们看到在获取连贯过程(connectToProviders())中,如果连接池中曾经存在则间接返回,不存在则须要从新建设连贯。如下:会初始化一个名为 JSF-CLI-CONN-#interfaceId 的线程池,在线程池中会执行对应工作,即获取到一个 ClientTransport 对象。
com.jd.jsf.gd.client.Client#connectToProviders
如果连贯(com.jd.jsf.gd.transport.ClientTransportFactory#initTransport),则:1)首先判断相干协定;
com.jd.jsf.gd.transport.ClientTransportFactory#initTransport
2)而后 BuildChannel 建设连贯(com.jd.jsf.gd.transport.ClientTransportFactory#BuildChannel),在这里会对应到 Server 端启动代码的相干参数,即是该处会监听服务器端的端口号,绑定到相应的 ip,并依据对应的通道做数据传输。
com.jd.jsf.gd.transport.ClientTransportFactory#BuildChannel
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)
总的来说,其提供者 Provider,消费者 Consumer,注册 Registry 等关系应该如下图所示:
Architecture
第一局部: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 对服务进行监控,治理或者降级容灾。
流程图
最终调用,如下:
com.jd.jsf.gd.filter.FilterChain#invoke
ResponseMessage response = this.filterChain.invoke(requestMessage);
参考资料
SPI:http://jnews.jd.com/circle-info-detail?postId=215333
NamespaceHandler:https://www.yisu.com/zixun/447867.html
jsf:https://cf.jd.com/pages/viewpage.action?pageId=132108361
netty:https://www.cnblogs.com/jing99/p/12515149.html
本次就先写到这,文章中如有问题,欢送留言斧正。也心愿能和更多气味相投的搭档沟通交流。后续会再更新对于 JSF 心跳检测、服务治理、服务反注册、钩子工具等模块细化的剖析以及以后咱们平台零碎初步接入 wormhole 平台(中间件 mesh 化)的一些教训分享。欢送大家点赞关注。