Talk about how microservices in different clusters are called through feign

foreword

The microservice call relationship of a project in the previous business department is as shown in the figure below


Later, due to the needs of business transformation, the project needs to deploy service A to another cluster, but service A still needs to be able to call service B. The calling relationship is as shown in the figure below. Before calling, the development team responsible for service B provides the corresponding feign client

package Given to the service A development team, the service A development team directly introduces the client package into the project, and activates the feign call through @EnableFeignClients. Now it spans different clusters, and the registry centers between the two clusters are different. The previous call The method is no longer applicable.

The technical person in charge of the business department came to our department to see if we had any plans. One of the solutions we provided at that time was that the service A team developed its own client interface to call service B, but the workload of this solution was relatively large. Another solution is to modify openfeign. There has always been a popular saying in the industry, there is nothing that cannot be solved by adding a layer

catastrophe

The plan we provide is as follows


In essence, service A directly calls service B, but now service A indirectly calls service B through the gateway of the same cluster as service B. The idea is already there, but we need to realize the business and change the code less, so that the demand can be realized

Implementation ideas

Enable the automatic service routing function based on the service registry through feign's url + gateway

Transformation steps

1. Custom annotation 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 {
    
    };
}

In fact, it is a copy of EnableFeignClients, the difference is that the imported bean is different

2. Extend the native FeignClientsRegistrar

The core content of the extension is as follows

 @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. The gateway enables the automatic service routing function based on the service registry

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

test

The test provides a consumer, service provider, gateway, registry

Remove the native EnableFeignClients annotation in the consumer startup class, and use our custom annotation EnableLybGeekFeignClients

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

}

Consumer application.yml opens feign call log

logging:
  level:
    # feign调用所在的包
    com.github.lybgeek.api.feign: debug


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

Calling the service provider through the consumer

can be accessed normally, and we observe the information output by the consumer console


We can find that this call is a call between services, indicating that our extended feign retains the capabilities of the original feign

We add the following content to the consumer's application.yml

lybgeek:
  gateWayUrl: localhost:8000

Then call the service provider through the consumer


It can be accessed normally. We observe the information output by the consumer console

and the information output by the gateway console.

We can find that this call is routed to the service through the gateway and then generated calls, indicating that our extended feign already has the ability to request services through the gateway. Ability

Summarize

Some friends may say, why bother to expand, directly through

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

No can also be achieved. In fact, if you consider the business scenario at that time, you will find that this method needs to be changed much more than directly extending feign, and once there is a problem, it is not easy to focus on rollback. Sometimes it is easy to go astray when talking about technical implementation out of business scenarios

demo link

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

Guess you like

Origin blog.csdn.net/kingwinstar/article/details/129963876