当前位置: 首页 > 知识库问答 >
问题:

创建自定义Spring Cloud Netflix Ribbon客户端

督辉
2023-03-14

我在Cloud Foundry环境中将Spring CloudNetflixRibbon与Eureka结合使用。

我尝试实现的用例如下:

>

  • 我有一个名为addres-service的正在运行的CF应用程序,其中包含多个实例。

    实例正在通过服务名称注册到Eureka

    我使用eureka向服务实例添加了自定义元数据。例子元数据映射。应用程序id:${vcap.application.application\u id}

    我想使用Eureka的InstanceInfo中的信息(尤其是元数据和有多少服务实例可用)来设置CF HTTP头“X-CF-APP-INSTANCE”,如下所述。

    这个想法是发送一个头文件,如"X-CF-APP-INSTANCE":"appIdFromMetadata: instanceIndexCompatedFromNoOfServiceInstance",从而在涉及到负载平衡时“推翻”CF的Go-路由器,如本问题底部所述。

    我认为要设置头,我需要创建一个定制的RibbonClient实现,也就是说,用Netflix的术语来说,就是这里描述的AbstractLoadBalanceraWaleClient的一个子类,并重写execute()方法。

    但是,这不起作用,因为Spring CloudNetflixRibbon不会从application.yml中读取我的CustomRibbonClient的类名。似乎Spring CloudNetflix围绕简单的Netflix东西包装了相当多的类。

    我尝试实现RetryableRibbonLoadBalancingHttpClientRibbonLoadBalancingHttpClient的子类,它们是Spring类。我尝试在application.yml中使用ribbon给出它们的类名。ClientClassName但不起作用。我尝试覆盖Spring Cloud的HttpClientRibbonConfiguration中定义的bean,但我无法让它工作。

    所以我有两个问题:

    >

    怎么做才合适?

    非常感谢您的任何想法,请提前感谢!

    更新-1

    我深入研究了这一点,发现了RibbonAutoConfiguration。

    这将创建一个SpringClientFactory,它提供一个只在RibbonClientHttpRequestFactory中使用的方法(也在RibbonAutoConfiguration中声明)。

    不幸的是,RibbonClientHttpRequestFactory将客户端硬编码到Netflix。而且似乎不可能重写SpringClientFactory,也不可能重写RibbonClientPrequestFactory。

    我想知道这是否可能。

  • 共有1个答案

    白才捷
    2023-03-14

    好的,我会自己回答这个问题,以防将来有人需要<实际上,我终于实现了它。

    TLDR-解决方案如下:https://github.com/TheFonz2017/Spring-Cloud-Netflix-Ribbon-CF-Routing

    解决方案:

    • 允许在Cloud Foundry上使用Ribbon,覆盖Go-路由器的负载平衡。
    • 将自定义路由标头添加到Ribbon负载平衡请求(包括重试),以指示CF的Go-路由器将请求路由到Ribbon选择的服务实例(而不是其自己的负载均衡器)。
    • 显示如何拦截负载平衡请求

    理解这一点的关键是Spring Cloud有自己的LoadBalancer框架,Ribbon只是其中一种可能的实现。理解Ribbon仅用作负载均衡器而不是HTTP客户端也很重要。换句话说,Ribbon的ILoadBalancer实例仅用于从服务器列表中选择服务实例。对所选服务器实例的请求由Spring Cloud的AbstractLoadBalancingClient的实现完成。使用Ribbon时,这些是RibbonLoadBalancingHttpClientRetryableRibbonLoadBalancingHttpClient的子类。

    因此,我最初向Ribbon的HTTP客户端发送的请求添加HTTP标头的方法没有成功,因为Ribbon的HTTP/Rest客户端实际上根本不被Spring Cloud使用。

    解决方案是实现一个Spring CloudLoadBalancerRequest estTransex,它(与其名称相反)是一个请求拦截器。

    我的解决方案使用以下实现:

    public class CFLoadBalancerRequestTransformer implements LoadBalancerRequestTransformer {
        public static final String CF_APP_GUID = "cfAppGuid";
        public static final String CF_INSTANCE_INDEX = "cfInstanceIndex";
        public static final String ROUTING_HEADER = "X-CF-APP-INSTANCE";
    
        @Override
        public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
    
            System.out.println("Transforming Request from LoadBalancer Ribbon).");
    
            // First: Get the service instance information from the lower Ribbon layer.
            //        This will include the actual service instance information as returned by Eureka. 
            RibbonLoadBalancerClient.RibbonServer serviceInstanceFromRibbonLoadBalancer = (RibbonLoadBalancerClient.RibbonServer) instance;
    
            // Second: Get the the service instance from Eureka, which is encapsulated inside the Ribbon service instance wrapper.
            DiscoveryEnabledServer serviceInstanceFromEurekaClient = (DiscoveryEnabledServer) serviceInstanceFromRibbonLoadBalancer.getServer();
    
            // Finally: Get access to all the cool information that Eureka provides about the service instance (including metadata and much more).
            //          All of this is available for transforming the request now, if necessary.
            InstanceInfo instanceInfo = serviceInstanceFromEurekaClient.getInstanceInfo();
    
            // If it's only the instance metadata you are interested in, you can also get it without explicitly down-casting as shown above.  
            Map<String, String> metadata = instance.getMetadata();
            System.out.println("Instance: " + instance);
    
            dumpServiceInstanceInformation(metadata, instanceInfo);
    
            if (metadata.containsKey(CF_APP_GUID) && metadata.containsKey(CF_INSTANCE_INDEX)) {
                final String headerValue = String.format("%s:%s", metadata.get(CF_APP_GUID), metadata.get(CF_INSTANCE_INDEX));
    
                System.out.println("Returning Request with Special Routing Header");
                System.out.println("Header Value: " + headerValue);
    
                // request.getHeaders might be immutable, so we return a wrapper that pretends to be the original request.
                // and that injects an extra header.
                return new CFLoadBalancerHttpRequestWrapper(request, headerValue);
            }
    
            return request;
        }
    
        /**
         * Dumps metadata and InstanceInfo as JSON objects on the console.
         * @param metadata the metadata (directly) retrieved from 'ServiceInstance'
         * @param instanceInfo the instance info received from the (downcast) 'DiscoveryEnabledServer' 
         */
        private void dumpServiceInstanceInformation(Map<String, String> metadata, InstanceInfo instanceInfo) {
            ObjectMapper mapper = new ObjectMapper();
            String json;
            try {
                json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(metadata);
                System.err.println("-- Metadata: " );
                System.err.println(json);
    
                json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(instanceInfo);
                System.err.println("-- InstanceInfo: " );
                System.err.println(json);
            } catch (JsonProcessingException e) {
                System.err.println(e);
            }
        }
    
        /**
         * Wrapper class for an HttpRequest which may only return an
         * immutable list of headers. The wrapper immitates the original 
         * request and will return the original headers including a custom one
         * added when getHeaders() is called. 
         */
        private class CFLoadBalancerHttpRequestWrapper implements HttpRequest {
    
            private HttpRequest request;
            private String headerValue;
    
            CFLoadBalancerHttpRequestWrapper(HttpRequest request, String headerValue) {
                this.request = request;
                this.headerValue = headerValue;
            }
    
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(request.getHeaders());
                headers.add(ROUTING_HEADER, headerValue);
                return headers;
            }
    
            @Override
            public String getMethodValue() {
                return request.getMethodValue();
            }
    
            @Override
            public URI getURI() {
                return request.getURI();
            }
        }  
    }
    

    类正在查找Eureka返回的服务实例元数据中设置CF App Instance Routing标头所需的信息。

    这些信息是

    • 实现服务的CF应用程序的GUID,其中存在多个用于负载平衡的实例

    您需要在服务的application.yml中提供如下内容:

    eureka:
      instance: 
        hostname: ${vcap.application.uris[0]:localhost}
        metadata-map:
          # Adding information about the application GUID and app instance index to 
          # each instance metadata. This will be used for setting the X-CF-APP-INSTANCE header
          # to instruct Go-Router where to route.
          cfAppGuid:       ${vcap.application.application_id}
          cfInstanceIndex: ${INSTANCE_INDEX}
    
      client: 
        serviceUrl:
          defaultZone: https://eureka-server.<your cf domain>/eureka
    

    最后,您需要在服务消费者(在引擎盖下使用Ribbon)的Spring配置中注册LoadBalancerRequest estTransex实现:

    @Bean
    public LoadBalancerRequestTransformer customRequestTransformer() {
      return new CFLoadBalancerRequestTransformer();
    }
    

    因此,如果您在服务使用者中使用@LoadBalance RestTemboard,模板将调用Ribbon选择要将请求发送到的服务实例,将发送请求,拦截器将注入路由标头。Go-路由器将将请求路由到路由标头中指定的确切实例,并且不会执行任何会干扰Ribbon选择的额外负载平衡。如果需要重试(针对相同或一个或多个下一个实例),拦截器将再次注入相应的路由标头——这次是针对Ribbon选择的可能不同的服务实例。这允许您有效地使用Ribbon作为负载平衡器,并事实上禁用Go-路由器的负载平衡,将其降级为仅仅是一个代理。好处是Ribbon是您可以(以编程方式)影响的东西,而您对Go-路由器几乎没有影响。

    注意:这是针对LoadBalanced RestTemplate和works进行的测试。但是,对于假客户机,它不是这样工作的。这篇文章描述了我最接近于解决这个问题的方法,但是,这里描述的解决方案使用了一个拦截器,该拦截器无法访问(功能区)所选的服务实例,因此不允许访问所需的元数据
    到目前为止,尚未找到适用于假装客户端的解决方案。

     类似资料:
    • 问题内容: 对于使用嵌套客户端的Elasticsearch非常新,我正在使用自定义分析器创建索引,但是在使用分析进行测试时,它似乎并未使用自定义分析器。主要没有Edgengram令牌出现。我缺少什么使我的自定义分析器成为索引的默认设置吗?当我使用elastichq检查映射时,它们会显示我的自定义分析器。 问题答案: 您已将自定义分析器添加到索引中,但是现在您需要将其应用到字段中。您可以在字段映射级

    • 问题内容: 我正在与socket.io聊天应用程序,我想用我的自定义客户端ID,而不是默认的(,)。连接时是否有任何发送自定义标识符的方式,或仅使用某种方式来跟踪每个ID的自定义名称?谢谢! 问题答案: 您可以在服务器上创建一个数组,并在其上存储自定义对象。例如,您可以存储Socket.io创建的ID和每个客户端发送到服务器的自定义ID: 在此示例中,您需要从每个客户端调用 storeClient

    • 如何转换react查询以适应自定义类型?? 注意:我在前端使用的是JavaScipt,而不是Typescript

    • 我尝试通过wsimport命令生成此命令。下面是我使用的命令。 wsimport-keep-b bindings.xml-p com.aasc.carrier.shipexec.proxy-implserviceName wcfSoxContract-importname wcfShip http://shipexec.com/demo/wcf/soap?wsdl-b-xautonameresol

    • 创建客户端有两种方式,一种是直接使用特化的构造器函数,另一种是使用工厂构造器函数。 第一种方式返回的是具体的客户端结构体指针对象,第二种方式返回的是客户端接口对象。 使用特化的构造器函数创建客户端 特化的构造器函数有下面几个: func NewHTTPClient(uri ...string) (client *HTTPClient) func NewTCPClient(uri ...string

    • 问题内容: 注释如何与Java一起使用?以及如何创建这样的自定义注释: 基本上,我需要保留的POJO在持久化时像这样进行序列化: 这样,实际的生成/持久对象是这样的: 任何想法如何实现这一点? 问题答案: 如果创建自定义注释,则必须使用此处的 API 示例进行处理。您可以参考如何声明注释。 这是Java中的示例注释声明的样子。 并被称为。 表示您想在运行时保留注释,并且可以在运行时访问它。 表示您