前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊不同集群的微服务如何通过feign调用

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

原创
作者头像
lyb-geek
发布2023-07-04 10:55:59
3380
发布2023-07-04 10:55:59
举报
文章被收录于专栏:Linyb极客之路

前言

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

4338526d0991398094a4813d4e4ee75c_89177ad4877dca80d41596fefe4308d6.png
4338526d0991398094a4813d4e4ee75c_89177ad4877dca80d41596fefe4308d6.png

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

47e7b49f7eefde96e322053de210945b_8c262a88a67d4ab0ac6e3f532bea307d.png
47e7b49f7eefde96e322053de210945b_8c262a88a67d4ab0ac6e3f532bea307d.png

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

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

破局

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

208c46fadd7aa69c690cfb55dd2c461e_7147a85278b3d00b1494d6065f6ae0d4.png
208c46fadd7aa69c690cfb55dd2c461e_7147a85278b3d00b1494d6065f6ae0d4.png

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

实现思路

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

改造步骤

1、自定义注解EnableLybGeekFeignClients

代码语言:java
复制
@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

扩展的核心内容如下

代码语言:java
复制
 @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开启基于服务注册中心自动服务路由功能

代码语言:yaml
复制
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

测试

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

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

代码语言:java
复制
@SpringBootApplication
@EnableLybGeekFeignClients(basePackages = "com.github.lybgeek")
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }

}

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

代码语言:yaml
复制
logging:
  level:
    # feign调用所在的包
    com.github.lybgeek.api.feign: debug


feign:
  client:
    config:
      default:
        # 开启feign记录请求和响应的标题、正文和元数据
        loggerLevel: FULL

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

30e127d3210420806280f75ddffbca7c_d87a8d411498d2670ec2b2524fb38d83.png
30e127d3210420806280f75ddffbca7c_d87a8d411498d2670ec2b2524fb38d83.png

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

ed3473bc18f8820463b30069971c3c8e_1540d8199f567638d323ada046140d63.png
ed3473bc18f8820463b30069971c3c8e_1540d8199f567638d323ada046140d63.png

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

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

代码语言:yaml
复制
lybgeek:
  gateWayUrl: localhost:8000

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

f1d38fd1cbaea735842175de16a0be88_5666a69a92faad9e23a959f035b481ca.png
f1d38fd1cbaea735842175de16a0be88_5666a69a92faad9e23a959f035b481ca.png

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

55c1bde41205df62032e99b3b2ea9afa_5a20867cf134e3f98d6059af1f6863db.png
55c1bde41205df62032e99b3b2ea9afa_5a20867cf134e3f98d6059af1f6863db.png

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

95dcb7c9122dee066f49bb3d9037de47_9e212f779ad3cb74cb2d046188a82f1b.png
95dcb7c9122dee066f49bb3d9037de47_9e212f779ad3cb74cb2d046188a82f1b.png

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

总结

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

代码语言:java
复制
@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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 破局
  • 实现思路
  • 改造步骤
  • 测试
  • 总结
  • demo链接
相关产品与服务
微服务引擎 TSE
微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档