在非 SpringBoot 工程中实现 Eureka 构建 Feign 服务

背景

最近有个需求需要嵌入至其他业务中,其中有个数据需要通过 Eureka 获取,正常情况下使用 @EnableDiscoveryClient@EnableFeignClients 即可获取服务接口实例。可是由于我使用了 BeanPostProcessor 对注解进行了切片代理,并且目标项目也存在非 SpringCloud 项目,那么便只能通过底层 API 构建服务方实例了。

方案

EurekaClient 提供了服务发现注册功能,但是在 Feign 所接收的属性中并没有用于负载均衡的,其中最有可能的就是 Client 属性了,这是用于负责服务请求的模块,那么就需要调研下是否能够通过 EurekaClient 构建出一个带有负载均衡功能的客户端。

创建 LBClientFactory

Feign 的官方 Wiki 提供了构建服务实例的 方法,但 Feign 并不提供构建所需属性的实现,而是由其他框架适配 Feign 所提供的接口来实现每个功能。

在分析 Ribbon 的 Client 实现 RibbonClient,其构造器接收一个 LBClientFactory 参数,负载均衡的功能就是由它提供。

通过搜索引擎发现了打通 Robbion 与 EurekaClient 的实现方式,需要引入依赖 <artifactId>ribbon-eureka</artifactId>

private Client newClient(String vipAddress, Provider<EurekaClient> provider) {
    ServerList<DiscoveryEnabledServer> list = new DiscoveryEnabledNIWSServerList(vipAddress, provider);
    ServerListFilter<DiscoveryEnabledServer> filter = new ZoneAffinityServerListFilter<DiscoveryEnabledServer>();

    ZoneAwareLoadBalancer<DiscoveryEnabledServer> lb = LoadBalancerBuilder.<DiscoveryEnabledServer>newBuilder()
            .withDynamicServerList(list)
            .withRule(new AvailabilityFilteringRule())
            .withServerListFilter(filter)
            .buildDynamicServerListLoadBalancer();

    return RibbonClient.builder().lbClientFactory(new LBClientFactory() {
        @Override
        public LBClient create(String clientName) {
            return LBClient.create(lb, ClientFactory.getNamedConfig(clientName));
        }
    }).build();
}

其中需要使用 DiscoveryEnabledNIWSServerList 用于发现服务集合,它接收两个提供两个参数,一个是项目的 vipAddress,一个是 Provider

创建 EurekaClient

Eureka 的官方 Wiki 中提供了 EurekaClient 的 构造方式,需要传入 EurekaClientConfigEurekaInstanceConfig 用来提供模块的配置信息。

在 netflix 提供的 eureka-client 模块下,提供了 DefaultEurekaClientConfigMyDataCenterInstanceConfig

这两个类默认将会读取 eureka-client.properties 下的配置,可以通过设置 eureka.client.props 参数更改读取的配置文件,也可以使用 ConfigurationManager 的静态方法来指定读取配置文件。

除此之外,spring-cloud 也提供了相应的实现类 EurekaClientConfigBeanEurekaInstanceConfigBean,通过 spring 的配置资源解析器将属性装配成对象使用。

入参已经拿到了,但是还未结束,在跟进 DiscoveryClient 代码实现中,发现他在构造方法中会对属性进行静态保存。

// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);

在这个项目中,有可能存在业务项目内部与嵌入模块使用了不同的 Eureka 服务,为了保持与业务项目的隔离,这里需要对官方提供的代码模版进行点改造。

private static volatile EurekaClient eurekaClient = null;

public EurekaClient initEurekaClient(EurekaInstanceConfig instanceConfig, EurekaClientConfig clientConfig) {
    if (null != eurekaClient) {
        return eurekaClient;
    }

    synchronized (DiscoveryManager.class) {
        if (null == eurekaClient) {
            DiscoveryClient discoveryClient = DiscoveryManager.getInstance().getDiscoveryClient();
            EurekaClientConfig eurekaClientConfig = DiscoveryManager.getInstance().getEurekaClientConfig();
            EurekaInstanceConfig eurekaInstanceConfig = DiscoveryManager.getInstance().getEurekaInstanceConfig();

            // create the client
            InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
            ApplicationInfoManager infoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
            infoManager.setInstanceStatus(InstanceInfo.InstanceStatus.STARTING);

            EurekaClient client = new DiscoveryClient(infoManager, clientConfig);
            infoManager.setInstanceStatus(InstanceInfo.InstanceStatus.UP);

            eurekaClient = client;

            DiscoveryManager.getInstance().setDiscoveryClient(discoveryClient);
            DiscoveryManager.getInstance().setEurekaClientConfig(eurekaClientConfig);
            DiscoveryManager.getInstance().setEurekaInstanceConfig(eurekaInstanceConfig);
        }
    }
    return eurekaClient;
}

在实际开发中可通过 DiscoveryManager 的静态属性对其进行管理的,避免重复构建。但是在这个场景下需要保证与业务之间的隔离,则以这种方式构建。

创建 Provider

在创建 LBClientFactory 的过程中,我们需要构建 DiscoveryEnabledNIWSServerList 用于服务的发现,由于构造方法接收的参数为 Provider<EurekaClient>,这里还需要对 EurekaClient 再经过包装后使用。

new Provider<EurekaClient>() {
    @Override
    public EurekaClient get() {
        return eurekaClient;
    }
};

构造服务实例

通过上述步骤基本完成了一个 EurekaClient 到 RobbionClient 的各个过程,最后代入 Feign 的创建方法中便可构造使用 Eureka 进行服务发现的实例。

public <T> T getInstance(Class<T> target, String namespace, Provider<EurekaClient> provider) {
    return Feign.builder()
        .client(newClient(namespace, provider))
        .encoder(new JacksonEncoder())
        .decoder(new JacksonDecoder())
        .options(new Request.Options(1000, 3000))
        .logLevel(Logger.Level.FULL)
        .target(target, "http://" + namespace);
}