前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Eureka] 九、远程通信模块:使用TransportClientFactory构建底层请求客户端完成服务注册、服务下线

[享学Eureka] 九、远程通信模块:使用TransportClientFactory构建底层请求客户端完成服务注册、服务下线

作者头像
YourBatman
发布2020-04-02 09:52:53
1.2K0
发布2020-04-02 09:52:53
举报
文章被收录于专栏:BAT的乌托邦

代码下载地址:https://github.com/f641385712/netflix-learning

前言

通过前两篇文章一步一步介绍了如何构建出一个JerseyApplicationClient实例来完成服务注册、服务下线等功能。而上文讲到了使用Eureka提供的EurekaJerseyClient通过Builder模式去构建已经比较方便了,但是仍旧存在一个小问题:无法摆脱Jersey关键字的感知,自然底层上也就无法摆脱对Jersey库的强依赖。

即使你并不需要使用Jersey的API来做事,但使用时你却明显的知道你实际就是在用Jersey来干活,似乎有点欲盖弥彰。这种“强耦合”很明显是制约了Eureka的弹性扩展性的,因此Eureka它提供了TransportClientFactory工厂用于屏蔽这一切,这便是本文讲述的内容。


正文

通过工厂方式创建实例的方式来解耦,是最为常用的一种设计模式。Eureka也不例外,使用该种模式能达到预期的效果。

当然喽,在此之前先要了解下Eureka中终端这个抽象概念:EurekaEndpoint


EurekaEndpoint 终端

EurekaEndpoint对于Eureka Client端来说就代表着一台远程的Server服务器。若S端是个集群,那么Client端便可获取到一个EurekaEndpoint的集合。该概念这在后文对Server集群的处理(ClusterResolver)时将会再次提及。

代码语言:javascript
复制
public interface EurekaEndpoint extends Comparable<Object> {
    String getServiceUrl();
    // 请使用getNetworkAddress方法代替此方法
    @Deprecated
    String getHostName();
    String getNetworkAddress();
    int getPort();
    boolean isSecure();
    String getRelativeUri();
}

接口方法见名之意,无需解释。它的继承图谱如下:

在这里插入图片描述
在这里插入图片描述

DefaultEndpoint

它的是Eureka的默认实现

代码语言:javascript
复制
public class DefaultEndpoint implements EurekaEndpoint {

    protected final String networkAddress;
    protected final int port;
    protected final boolean isSecure;
    protected final String relativeUri;
    protected final String serviceUrl;

	// serviceUr来自于配置,一般如:http://localhost:8080/eureka/
    public DefaultEndpoint(String serviceUrl) {
        this.serviceUrl = serviceUrl;
        try {
            URL url = new URL(serviceUrl);
            this.networkAddress = url.getHost();
            this.port = url.getPort();
            this.isSecure = "https".equals(url.getProtocol());
            this.relativeUri = url.getPath();
        } catch (Exception e) {
            throw new IllegalArgumentException("Malformed serviceUrl: " + serviceUrl);
        }
    }

	// 当然还提供了一个更为原子的构造器,也较为常用
	public DefaultEndpoint(String networkAddress, int port, boolean isSecure, String relativeUri) { ... }


    @Deprecated
    @Override
    public String getHostName() {
        return networkAddress;
    }
    @Override
    public String getNetworkAddress() {
        return networkAddress;
    }
    ...
}

另外,它还提供了一个静态方法:根据hostNames批量创建出List<EurekaEndpoint>

其实这里叫根据networkAddresses批量创建更为合适,但因为历史原因,方法名不便修改~

代码语言:javascript
复制
DefaultEndpoint:

    public static List<EurekaEndpoint> createForServerList(
            List<String> hostNames, int port, boolean isSecure, String relativeUri) {
        if (hostNames.isEmpty()) {
            return Collections.emptyList();
        }
        List<EurekaEndpoint> eurekaEndpoints = new ArrayList<>(hostNames.size());
        for (String hostName : hostNames) {
            eurekaEndpoints.add(new DefaultEndpoint(hostName, port, isSecure, relativeUri));
        }
        return eurekaEndpoints;
    }

代码示例
代码语言:javascript
复制
@Test
public void fun6() {
    EurekaEndpoint endpoint = new DefaultEndpoint("http://192.168.1.100:8080/eureka/");
    System.out.println(endpoint.getServiceUrl()); // http://localhost:8080/eureka/
    System.out.println(endpoint.getNetworkAddress()); // 192.168.1.100
    System.out.println(endpoint.getHostName()); // 192.168.1.100
    System.out.println(endpoint.getPort()); // 8080
    System.out.println(endpoint.isSecure()); // false
    System.out.println(endpoint.getRelativeUri()); // /eureka/


    // 静态方法测试  isSecure=true那用的就是https
    List<EurekaEndpoint> endpoints = DefaultEndpoint.createForServerList(Arrays.asList("192.168.1.1", "192.168.1.2"), 8080, true, "/eureka/");
    // [DefaultEndpoint{ serviceUrl='https://192.168.1.1:8080/eureka/}, DefaultEndpoint{ serviceUrl='https://192.168.1.2:8080/eureka/}]
    System.out.println(endpoints);
}

AwsEndpoint

它是DefaultEndpoint的子类,使用在AWS云环境。额外增加了zone、region属性:

代码语言:javascript
复制
public class AwsEndpoint extends DefaultEndpoint {

    protected final String zone;
    protected final String region;
	...
	// 并且重新提供了静态方法createForServerList
	public static List<AwsEndpoint> createForServerList(List<String> hostNames, int port, boolean isSecure, String relativeUri, String region,String zone) {
		...
        for (String hostName : hostNames) {
            awsEndpoints.add(new AwsEndpoint(hostName, port, isSecure, relativeUri, region, zone));
        }
		...
	}
}



TransportClientFactory 工厂

EurekaHttpClient创建工厂接口,负责创建low level低等级的Client(后面会讲还有用于创建高级的EurekaHttpClient的工厂:EurekaHttpClientFactory),所以你看它的命名是Transport含义,表明自己是用于数据传输级别的。

代码语言:javascript
复制
public interface TransportClientFactory {
	// 根据终端EurekaEndpoint创建一个底层的可发送Http请求的Client
    EurekaHttpClient newClient(EurekaEndpoint serviceUrl);
    void shutdown();
}

该接口的主要实现便是JerseyEurekaHttpClientFactory,基于Jersey1.xApacheHttpClient4)实现的,用于创建JerseyApplicationClient客户端。


JerseyEurekaHttpClientFactory

该工厂顾名思义:用于创建JerseyApplicationClient实例。

成员属性:

代码语言:javascript
复制
public class JerseyEurekaHttpClientFactory implements TransportClientFactory {
	
    private final EurekaJerseyClient jerseyClient;
    private final ApacheHttpClient4 apacheClient;
    private final ApacheHttpClientConnectionCleaner cleaner;
    private final Map<String, String> additionalHeaders;

}
  • EurekaJerseyClient jerseyClient:为了得到一个ApacheHttpClient4而构建,当然它可以为null,因为外部可以把构件好ApacheHttpClient4直接传进来
  • ApacheHttpClient4 apacheClient:构建JerseyApplicationClient是需要它的,而构建它是通过高级的EurekaJerseyClient来完成的(默认)
  • ApacheHttpClientConnectionCleaner cleanerApacheHttpClient4 apacheClient的黄金搭档
  • Map<String, String> additionalHeaders:额外的http请求头

初始化:

代码语言:javascript
复制
JerseyEurekaHttpClientFactory:

	// jerseyClient是可以为null的
	// 若jerseyClient不为null,apacheClient由它来提供,否则接受外部传递的
	// 若jerseyClient为null才重新开启一个Cleaner任务,否则无需重新开启(因为jerseyClient内部自己会有定时清理任务)
    private JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient,
                                          ApacheHttpClient4 apacheClient,
                                          long connectionIdleTimeout,
                                          Map<String, String> additionalHeaders) {
        this.jerseyClient = jerseyClient;
        this.apacheClient = jerseyClient != null ? jerseyClient.getClient() : apacheClient;
        this.additionalHeaders = additionalHeaders;
        if (jerseyClient == null) {
            // the jersey client contains a cleaner already so only create this cleaner if we don't have a jersey client
            this.cleaner = new ApacheHttpClientConnectionCleaner(this.apacheClient, connectionIdleTimeout);
        } else { // 强制指定为null
            this.cleaner = null;
        }
    }

	... // 说明:它的public构造器都被标记为了@Deprecated,是因为只想你通过builder的方式去构建

工厂方法:创建一个JerseyApplicationClient实例

代码语言:javascript
复制
JerseyEurekaHttpClientFactory:

    @Override
    public EurekaHttpClient newClient(EurekaEndpoint endpoint) {
        return new JerseyApplicationClient(apacheClient, endpoint.getServiceUrl(), additionalHeaders);
    }

构建方式

为了构建一个JerseyEurekaHttpClientFactory实例,Eureka提供了多种方式:

静态方法:

代码语言:javascript
复制
JerseyEurekaHttpClientFactory:

	// ClientFilter:它是Jersey源生API的一个Filter。能够修改出站HTTP请求或入站HTTP响应
	// 常见的实现有:HTTPBasicAuthFilter  鉴权
	// LoggingFilter:打日志
	// GZIPContentEncodingFilter:支持Gzip压缩(这样response就会以压缩格式返回,通过此过滤器解压)
	// myInstanceInfo:实例信息
	// AbstractEurekaIdentity:用于表示Client客户端的唯一表示(id、name、version等)
    public static JerseyEurekaHttpClientFactory create(EurekaClientConfig clientConfig,
										            Collection<ClientFilter> additionalFilters,
										            InstanceInfo myInstanceInfo,
										            AbstractEurekaIdentity clientIdentity) {
        return create(clientConfig, additionalFilters, myInstanceInfo, clientIdentity, Optional.empty(), Optional.empty());
    }
    // 支持SSL的 很少使用
    public static JerseyEurekaHttpClientFactory create(...) {...}

Builder方式:若上面两个create()不是你想要的,那你也可以使用Builder方式自定义构建:

代码语言:javascript
复制
JerseyEurekaHttpClientFactory:

	// 使用它居多:不开启使用是特性
	// 当然你是可以通过EurekaClientConfig#getExperimental来配置的,默认是不开启的
    public static JerseyEurekaHttpClientFactoryBuilder newBuilder() {
        return new JerseyEurekaHttpClientFactoryBuilder().withExperimental(false);
    }
    public static JerseyEurekaHttpClientFactoryBuilder experimentalBuilder() {
        return new JerseyEurekaHttpClientFactoryBuilder().withExperimental(true);
    }

JerseyEurekaHttpClientFactoryBuilder继承的抽象父类:EurekaClientFactoryBuilder,管理着非常非常多的属性,而这些属性值基本来自于配置EurekaClientConfig(前文已介绍)。

说明:JerseyEurekaHttpClientFactoryBuilder的构造器是public的,若你愿意也可以自己创建一个Builder实例来构建实体

它的build()方法如下:

代码语言:javascript
复制
JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder:

    @Override
    public JerseyEurekaHttpClientFactory build() {
        Map<String, String> additionalHeaders = new HashMap<>();
        if (allowRedirect) {
        	// X-Discovery-AllowRedirect:true
            additionalHeaders.put(HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true");
        }
        if (EurekaAccept.compact == eurekaAccept) {
        	// X-Eureka-Accept:full(全部的) or compact(秀珍的)
            additionalHeaders.put(EurekaAccept.HTTP_X_EUREKA_ACCEPT, eurekaAccept.name());
        }

		// 若开启了实验室功能,就单独构建试验室实例返回
        if (experimental) {
            return buildExperimental(additionalHeaders);
        }
        // 大部分会是经过此方法,构建普通实例
        return buildLegacy(additionalHeaders, systemSSL);
    }


	// =====这里面的代码已经很熟悉了,基本同上篇文章的示例代码=====
	private JerseyEurekaHttpClientFactory buildLegacy(Map<String, String> additionalHeaders, boolean systemSSL) {
		
		// 通过EurekaJerseyClientBuilder目的是构建出一个EurekaJerseyClient
	    EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder()
	             .withClientName(clientName)
	             .withUserAgent("Java-EurekaClient")
	             .withConnectionTimeout(connectionTimeout)
	             .withReadTimeout(readTimeout)
	             .withMaxConnectionsPerHost(maxConnectionsPerHost)
	             .withMaxTotalConnections(maxTotalConnections)
	             .withConnectionIdleTimeout((int) connectionIdleTimeout)
	             .withEncoderWrapper(encoderWrapper)
	             .withDecoderWrapper(decoderWrapper)
	             .withProxy(proxyHost,String.valueOf(proxyPort),proxyUserName,proxyPassword);
		...
		EurekaJerseyClient jerseyClient = clientBuilder.build();
		
		... //给Jersey添加上必要的ClientFilter,如EurekaIdentityHeaderFilter、GZIPContentEncodingFilter等等
		
		return new JerseyEurekaHttpClientFactory(jerseyClient, additionalHeaders);
	}

使用TransportClientFactories构建工厂实例
代码语言:javascript
复制
public interface TransportClientFactories<F> {
    
    @Deprecated
    public TransportClientFactory newTransportClientFactory(final Collection<F> additionalFilters,
                                                                   final EurekaJerseyClient providedJerseyClient);
    public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
                                                                   final Collection<F> additionalFilters,
                                                                   final InstanceInfo myInstanceInfo);
    public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
            final Collection<F> additionalFilters,
            final InstanceInfo myInstanceInfo,
            final Optional<SSLContext> sslContext,
            final Optional<HostnameVerifier> hostnameVerifier);
}

实现仅有Jersey1TransportClientFactories,一般这么用:new Jersey1TransportClientFactories()#xxx,有了builder,该方法有点多此一举的赶脚。

通过它产出的Client,唯一就是在其基础增加了MetricsCollecting指标信息收集的功能~


代码示例

下面演示的是标准的、使用工厂方式来构建请求客户端来完成注册的实例。

代码语言:javascript
复制
@Test
public void fun11() {
    // TransportClientFactory factory = JerseyEurekaHttpClientFactory.create();
    TransportClientFactory factory = JerseyEurekaHttpClientFactory.newBuilder()
		 .withClientName("YoutBatman-Client") //必填
		 .build();
		 
    // 准备远端Server的地址
    EurekaEndpoint endpoint = new DefaultEndpoint("http://localhost:8761/eureka/");
    EurekaHttpClient client = factory.newClient(endpoint);


    // 注册服务
    EurekaHttpResponse<Void> response = client.register(InstanceInfo.Builder.newBuilder()
            .setInstanceId("account-002")
            .setHostName("localhost")
            .setIPAddr("127.0.0.1")
            .setDataCenterInfo(new MyDataCenterInfo(DataCenterInfo.Name.MyOwn))
            .setAppName("account") // 大小写无所谓
            .build());
    System.out.println("注册成功,状态码:" + response.getStatusCode());
}

运行程序,Server端页面情况如下阶段(完美,注册成功):

在这里插入图片描述
在这里插入图片描述

总结

关于远程通信模块:使用TransportClientFactory构建底层请求客户端完成服务注册、服务下线就介绍到这,可以说这是使用者标准的使用姿势,供以你学习和参考。

当然喽,和Server端的通讯不可能需要我们手动构建,也不可能直接是赤裸裸的单连,重试呢?会话保持呢?这些高级功能都还木有,而其实这些Eureka本身也都有提供,请接着下文继续学习。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • EurekaEndpoint 终端
      • DefaultEndpoint
    • TransportClientFactory 工厂
      • JerseyEurekaHttpClientFactory
      • 使用TransportClientFactories构建工厂实例
    • 代码示例
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档