scservers(四)服务调用——接口与负载均衡

前言

前面的三篇搭起微服务的基本应用框架:服务发现,分布式动态配置,消息总线的通知能力等。就如人的身体基本上已经有了头脑四肢,基本成形。

头脑四肢之间需要互联互通,相当于服务调用,并且需要一定的协议接口及基于协议的序列化反序列化,比如dubbo,grpc甚至原生的别人不了解的协议等。目前我们这里只介绍下http的,其他基本类同。服务的调用还要负载均衡,不然只用右手,容易起茧。

http-rest

目前http协议所使用的远程调用范式基本以REST为范式,spring3之后就提供RestTemplate来执行rest http接口调用,并提供序列化反序列的能力。但没有负载均衡,负载均衡依赖于服务注册,所以springcloud 提供了Ribbon作为软负载。

而Feign,则是及大成者,包含软件负载(ribbon)的http远程调用及序列化反序列化能力。即有如下方式:

* 原生RestTemplate+Ribbon

* Feign

RestTemplate+Ribbon

  • 配置

    我们需要添加Ribbon的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

    但是在示例里面我们并没有在pom.xml中看到这个依赖配置,那是因为我们已经存在如下依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    前面我们说过,负载还依赖于服务注册,而如果使用eureka作为服务注册,那么其已经包含了Ribbon的依赖了。如果使用其他服务注册发现中心,如consul、zookeeper、etcd,那就要加上Ribbon的依赖。嗯,全家桶确实名不虚传。

    eureka的配置这里就不再复述了。

  • 使用

    • step1:@LoadBalanced

      在RestTemplate加上注解@LoadBalanced,让RestTemplate支持负载客户端。
    @Configuration
     class MyConfiguration {
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
    return new RestTemplate();
    }
    }
    • step2:配置负载模式

      标明Ribbon负载的工作模式,如果是轮训可以跳过这一步(默认支持轮训),如果想该用其他方式按照如下方式。已经实现了随机模式,我们也可以通过重写类支持其他模式。
    @Configuration
    public class RibbonRuleConfiguration {
    @Bean
    public IRule ribbonRule() {
    return new RandomRule();
    }
    }

    在使用到的control或者service加上使用的ribbon客户端的注解:

    @RibbonClient(name = "center-ribbon-Rule",configuration = RibbonRestTemplateConfiguration.class)
    public class AppApplication {
    
    • step3:使用 RestTemplate远程调用
    restTemplate.getForObject("http://serviceid/restpath", String.class);
    

    Eureka根据 spring.application.name 设置 serviceId,我们只要设置serviceid就可以解析出ip:port。

Feign

Feign 通过注解及拦截器让 Java HTTP 客户端编写更方便。支持可插拔编码器和解码器,降低 HTTP API 的复杂度,通过最少的资源和代码来实现和 HTTP API 的连接。通过可定制的解码器和错误处理,可以编写任意的HTTP API。Spring Cloud Feign 封装了 Ribbon 这一组件,所以在使用 Feign 同时还能提供负载均衡的功能,这一切只需要一个 @FeignClient 即可完成。

  • 配置

    新增依赖 spring-cloud-starter-openfeign

    <dependencies>
        <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    </dependencies>
  • 使用

    1. feign client 注解引入

      在application引入 feign注解

      @EnableFeignClients
      
    2. feign client 接口

      定义rpc服务的接口

      @FeignClient( name = "center-demo", path = "/test", decode404 = true)
      public interface DemoFeignclient {
      @GetMapping("/config")
      String selectconfigByName(@RequestParam String name);
      }
    3. 调用接口

      在service中实例化该接口并使用

          @Autowired
      private DemoFeignclient feignClient;
      
      @RequestMapping("/test/feign")
      public String testconfigrpcByFeign(String name) {
      return "Feign get username <<==>> "+feignClient.selectconfigByName(name);
      }

      我们看到,所有服务提供者供外部调用的接口是一样的,如果每个不同消费者都些feign client 接口,就做了重复的工作,这就是坏味道,所以我们可以像dubbo一样在写服务提供者的时候,就可以把接口单独整个架包出来,供消费者使用。具体可见后面章节的最佳实践。

  • 拦截器

    上面是使用 Feign 来调用简单的远程服务,但实际上远程服务一般会有权限验证(这部分后续章节也会出),需要在 header 中传递 token之类的。在方法中显示传递又过于麻烦了,这时候就可以考虑使用 Feign 提供的RequestInterceptor 接口,只要实现了该接口,那么Feign每次做远程调用之前都可以被它拦截下来在进行包装。示例如下,就可以方便的传递header字段了。

    @Configuration
    public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
    requestTemplate.header("token", "ok");
    }
    }
  • 源码

  • PS注意

    Feign与显示声明的Ribbon不要混合在一个controller下使用,不然使用Feign的时候会间歇性提示404错误

          {"timestamp":"2019-03-21T05:37:41.540+0000","status":404,"error":"Not Found","message":"No message available","path":"/test/token/back"}
    

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>