Spring Cloud 负载均衡器架构选型

优质博文:IT-BLOG-CN

我们这次项目主要从RestTemplate 和 Feign 进行选型分析。

一、Spring Cloud Feign分析

Feign是另外一种客户端负载均衡实现。

我在该模块写了Feign Client的示例代码。
【1】spring-cloud-web-demo-api为服务的sdk模块
【2】spring-cloud-web-demo-service为提供接口服务的模块
【3】spring-cloud-web-demo-client为模拟调用服务的模块

首先在spring-cloud-web-demo-api模块,定义Feign API。spring-cloud-web-demo为spring-cloud-web-demo-service暴露的服务名。

@FeignClient(value = "spring-cloud-web-demo")
public interface UserFeign {

    @GetMapping(value = "/user/getUserById", produces = "application/json;charset=utf-8")
    Object getUserById(@RequestParam(value = "id", required = false) Long id);

    //省略
}

然后通过ClientAutoConfiguration自动装配。(client直接引入api包就可以使用,不需要再EnableFeignClients)

@Configuration
@EnableFeignClients("net.teaho.demo.spring.cloud.web.api")
public class ClientAutoConfiguration {
}

org.springframework.boot.autoconfigure.EnableAutoConfiguration=

net.teaho.demo.spring.cloud.web.api.config.ClientAutoConfiguration

在service模块如以往Spring MVC般实现api模块接口即可。

@RestController
public class UserController implements UserFeign {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Override
    public Object getUserById(Long id) {
        return "{"id":1, "name": "test"}";
    }

    //省略
}

在Client模块,注入bean后直接调用。

@Component
@Slf4j
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    public Object getOneUser(){
        return userController.getUserById(1L);
    }
}

二、RestTemplate分析

写了具有客户端负载均衡能力的RestTemplate的请求代码。

类似这样定义:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

RestTemplate究竟是如何利用注册中心实现客户端负载均衡的呢?

实现方式: 就是将上面所说的LoadBalancerInterceptor负载均衡拦截器加到标注了@LoadBalanced的RestTemplate实例中。 LoadBalancerInterceptor拦截器会在执行过程中获取并设置适合的目标请求实例,重新构造请求URI。

// 将配置中标注了@LoadBalanced的RestTemplate注入到这里
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

//将注册的RestTemplateCustomizer(RestTemplate自定义器)集合处理上面的restTemplates集合
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
        final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
        for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
            for (RestTemplateCustomizer customizer : customizers) {
                customizer.customize(restTemplate);
            }
        }
    });
}

三、技术选型

最终选择使用OpenFeign,下面说说原因。

和RestTemplate比起来,OpenFeign显得更适合Spring Boot微服务。

Open Feign相当于(HTTP)RPC,相比起RestTemplate,它直接显式将API声明以JAVA接口形式标识出来。 并且因为底层用的动态代理,它还可以(无感知地)替换底层实现。比如,github上就有替换底层逻辑的repo – Open Feign+Dubbo的RPC实现。

通过sdk包的形式,方便了调用,不需要像RestTemplate一样,客户端自行拼接上一串请求参数。在代码编写上也清晰。

要使用就必须知道OpenFeign是怎么实现的呢?

四、OpenFeign 初始化分析

流程图如下:
在这里插入图片描述
看看前面例子里我们引入的OpenFeign的东西
【1】@EnableFeignClients(“net.teaho.demo.spring.cloud.web.api”)
【2】@FeignClient(value = “spring-cloud-web-demo”) 还有自动装配引入的
【3】FeignRibbonClientAutoConfiguration
【4】FeignClientsConfiguration

我们就从这两个注解开始分析源码。
【1】首先看@FeignClient注解。

//给接口标注成一个REST调用方
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
    //服务名,可以带协议前缀,也可以用${property.name}关联一个配置值。
    @AliasFor("name")
    String value() default "";

    @Deprecated
    String serviceId() default "";

    //bean name
    String contextId() default "";

    @AliasFor("value")
    String name() default "";

    /**
     * Sets the <code>@Qualifier</code> value for the feign client.
     */
    String qualifier() default "";
    //直接指定一个地址,比如http://localhost:12345,一般用于调试
    String url() default "";

    boolean decode404() default false;

    /**
     * A custom <code>@Configuration</code> for the feign client. 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}.
     *
     * @see FeignClientsConfiguration for the defaults
     */
    //可用于覆盖FeignClient默认设置
    Class<?>[] configuration() default {};
    //回滚类,像我的例子中定义的回滚类必须实现UserFeign接口,看https://clou