美文网首页程序员
聊聊不同集群的微服务如何通过feign调用

聊聊不同集群的微服务如何通过feign调用

作者: linyb极客之路 | 来源:发表于2023-07-03 10:44 被阅读0次

    前言

    之前业务部门的某项目微服务调用关系如下图

    4338526d0991398094a4813d4e4ee75c_89177ad4877dca80d41596fefe4308d6.png

    后因业务改造需要,该项目需要将服务A部署到另外一个集群,但服务A仍然需要能调用到服务B,调用关系如下图


    47e7b49f7eefde96e322053de210945b_8c262a88a67d4ab0ac6e3f532bea307d.png

    之前调用方式是负责服务B的开发团队提供相应的feign客户端包给到服务A开发团队,服务A开发团队直接将客户端包引入到项目,在通过@EnableFeignClients来激活feign调用,现在跨了不同集群,而且2个集群间的注册中心也不一样,之前的调用方式就不大适用了。

    业务部门的技术负责人就找到我们部门,看我们有没有什么方案。当时我们提供的方案,一种是服务A团队自己开发客户端接口去调用服务B,但这个方案工作量比较大。另外一种方案,就是通过改造openfeign。在业内一直很流行一句话,没有什么是加一层解决不了的

    破局

    后面我们提供的方案如下图

    208c46fadd7aa69c690cfb55dd2c461e_7147a85278b3d00b1494d6065f6ae0d4.png

    本质上就是原来服务A直接调用服务B,现在是服务A先通过和服务B同集群的网关,间接调用服务B。思路已经有了,但是我们需要实现业务能够少改代码,就能实现该需求

    实现思路

    通过feign的url + gateway开启基于服务注册中心自动服务路由功能

    改造步骤

    1、自定义注解EnableLybGeekFeignClients

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(LybGeekFeignClientsRegistrar.class)
    public @interface EnableLybGeekFeignClients {
    
        /**
         * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
         * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
         * {@code @ComponentScan(basePackages="org.my.pkg")}.
         * @return the array of 'basePackages'.
         */
        String[] value() default {};
    
        /**
         * Base packages to scan for annotated components.
         * <p>
         * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
         * <p>
         * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
         * package names.
         * @return the array of 'basePackages'.
         */
        String[] basePackages() default {};
    
        /**
         * Type-safe alternative to {@link #basePackages()} for specifying the packages to
         * scan for annotated components. The package of each class specified will be scanned.
         * <p>
         * Consider creating a special no-op marker class or interface in each package that
         * serves no purpose other than being referenced by this attribute.
         * @return the array of 'basePackageClasses'.
         */
        Class<?>[] basePackageClasses() default {};
    
        /**
         * A custom <code>@Configuration</code> for all feign clients. Can contain override
         * <code>@Bean</code> definition for the pieces that make up the client, for instance
         * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
         *
         * @return list of default configurations
         */
        Class<?>[] defaultConfiguration() default {};
    
        /**
         * List of classes annotated with @FeignClient. If not empty, disables classpath
         * scanning.
         * @return list of FeignClient classes
         */
        Class<?>[] clients() default {};
    }
    
    

    其实是照搬EnableFeignClients,差别只是import的bean不一样

    2、扩展原生的FeignClientsRegistrar

    扩展的核心内容如下

     @SneakyThrows
        private void registerFeignClient(BeanDefinitionRegistry registry,
                                         AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
            String className = annotationMetadata.getClassName();
            Class feignClientFactoryBeanClz = ClassUtils.forName("org.springframework.cloud.openfeign.FeignClientFactoryBean",Thread.currentThread().getContextClassLoader());
            String name = getName(attributes);
            String customUrl = getCustomUrl(getUrl(attributes),name);
            。。。省略其他代码
          
        }
    
        private String getCustomUrl(String url,String serviceName){
            if(StringUtils.hasText(url)){
                return url;
            }
            String gateWay = environment.getProperty("lybgeek.gateWayUrl");
            if(StringUtils.isEmpty(gateWay)){
                return url;
            }
    
            if(serviceName.startsWith("http://")){
                serviceName = StrUtil.trim(serviceName.replace("http://",""));
            }
    
            String customUrl = URLUtil.normalize(gateWay + "/" + serviceName);
    
            log.info("feign customed with new url:【{}】",customUrl);
    
            return customUrl;
    
        }
    

    3、gateway开启基于服务注册中心自动服务路由功能

    spring:
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true
              lower-case-service-id: true
    

    测试

    测试提供一个消费者、服务提供者、网关、注册中心

    在消费者的启动类去掉原生的EnableFeignClients注解,采用我们自定义注解EnableLybGeekFeignClients

    @SpringBootApplication
    @EnableLybGeekFeignClients(basePackages = "com.github.lybgeek")
    public class ConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class);
        }
    
    }
    
    

    消费者application.yml开启feign调用日志

    logging:
      level:
        # feign调用所在的包
        com.github.lybgeek.api.feign: debug
    
    
    feign:
      client:
        config:
          default:
            # 开启feign记录请求和响应的标题、正文和元数据
            loggerLevel: FULL
    

    通过消费端调用服务提供者


    30e127d3210420806280f75ddffbca7c_d87a8d411498d2670ec2b2524fb38d83.png

    可以正常访问,我们观察消费者控制台输出的信息

    ed3473bc18f8820463b30069971c3c8e_1540d8199f567638d323ada046140d63.png

    我们可以发现,此次调用,是服务与服务之间的调用,说明我们扩展的feign保留了原本feign的能力

    我们对消费者的application.yml,新增如下内容

    lybgeek:
      gateWayUrl: localhost:8000
    

    再通过消费端调用服务提供者

    f1d38fd1cbaea735842175de16a0be88_5666a69a92faad9e23a959f035b481ca.png

    可以正常访问,我们观察消费者控制台输出的信息


    55c1bde41205df62032e99b3b2ea9afa_5a20867cf134e3f98d6059af1f6863db.png

    同时观察网关控制台输出的信息


    95dcb7c9122dee066f49bb3d9037de47_9e212f779ad3cb74cb2d046188a82f1b.png

    我们可以发现,此次调用,是通过网关路由到服务再产生调用,说明我们扩展的feign已经具备通过网关请求服务的能力

    总结

    可能有朋友会说,何必这么麻烦扩展,直接通过

    @FeignClient(name = "${feign.instance.svc:provider}",url="${lybgeek.gateWayUrl: }/${feign.instance.svc:provider}",path = InstanceServiceFeign.PATH,contextId = "instance")
    

    不也可以实现。其实如果带入当时的业务场景考虑,就会发现这种方式,需要改的地方比直接扩展feign多得多,而且一旦出问题,不好集中回滚。有时候脱离业务场景,去谈论技术实现,会容易走偏

    demo链接

    https://github.com/lyb-geek/springboot-cloud-metadata-ext

    相关文章

      网友评论

        本文标题:聊聊不同集群的微服务如何通过feign调用

        本文链接:https://www.haomeiwen.com/subject/xlmqudtx.html