springcloud中使用Ribbon和Feign调用服务以及服务的高可用

1.消费者使用Ribbon组件负载均衡的调用服务者接口

在上一节中只介绍了如何将服务者和消费者注册到Eureka注册中心中,消费者并没有调用服务者,现在开始介绍,首先为了避免混淆,不再用上一节的消费者,重新建一个名为microservice-consumer-movie-ribbon的消费者,代码跟microservice-consumer-movie的一样,下面介绍如何负载均衡的调用。

1.1 使用Ribbon默认的轮询方式调用服务者

  • 引入jar包
    如果调用者要使用Ribbon实现调用服务者的负载均衡首先想到的是引入Ribbon的jar包,这里Eureka中已经包含了此jar,我们上一节已经引入了Eureka的jar,这里可以不用再引入了。
  • Main方法中引入@LoadBalanced负载均衡注解
    在MicroserviceConsumerMovieRibbonApplication的创建调用实例对象RestTemplate中加入注解@LoadBalanced注解,默认是轮询。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootApplication
    @EnableEurekaClient
    public class MicroserviceConsumerMovieRibbonApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }
    public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieRibbonApplication.class, args);
    }
    }
  • 启动两个或两个以上服务者进行测试
    启动注册中项目和服务者,消费者项目,这里因为要测负载均衡,所以至少要启动两个服务者才行。启动完第一个服务者之后,修改application.yml的端口号,接着启动第二个服务者,如下图:
    这里写图片描述
    经过以上步骤即可创建另一个服务者实例MicroserviceProviderUserApplication2,启动它将其注册到注册中心中。
    访问消费者的项目,多刷新一次会发现交替调用服务者MicroserviceProviderUserApplication和MicroserviceProviderUserApplication2,测试成功。

    1.2自定义负载均衡算法调用服务者

    可以自己定义一个负载均衡策略的类,我这里定义一个随机策略。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * @author 刘俊重
    * @Description 负载均衡配置,配置调用者调用服务者的调用策略
    * @date 13:56
    */
    @Configuration
    public class BalanceConfiguration {
    /**
    * @Description 定义随机调用的负载均衡算法
    */
    @Bean
    public IRule ribbonRule(){
    return new RandomRule();
    }
    }

在Main方法中,RibbonClient注解中指明调用某个服务使用某个指定的策略。

1
@RibbonClient(name="microservice-provider-user",configuration = BalanceConfiguration.class)

调用microservice-provider-user服务时,使用BalanceConfiguration定义的负载均衡策略,完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="microservice-provider-user",configuration = BalanceConfiguration.class)
public class MicroserviceConsumerMovieRibbonApplication {

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

public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieRibbonApplication.class, args);
}
}

注意:所有用到Ribbon负载均衡的均要有@LoadBalanced注解,只是我们没有定义自己的调用策略时,默认会使用轮询的策略。同时这里有坑,不能把定义的策略类BalanceConfiguration放在Main方法同级或子包下面,会有冲突(或者你自己用注解@ComponentScan再写方法,不让spring扫描这个自定义的策略类),官方文档中写的非常清楚,不懂的可以看看:https://springcloud.cc/spring-cloud-dalston.html#_customizing_the_ribbon_client

1.3使用配置文件自定义RibbonClient实现客户端负载均衡

springcloud从1.2.0版本之后支持通过自定义配置文件的方式配置负载均衡策略。官方在application.yml中这样配置的:

1
2
3
users:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

你只需要把users改成你的服务提供者的应用名称即可,WeightedResponseTimeRule可以配置成其他的策略,这里为了不影响其它代码,我再重新创建一个microservice-consumer-movie-ribbon-custom-properties项目。之前Main方法的@RibbonClient可以去掉了,application.yml中这么配置:

1
2
3
microservice-provider-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

这段配置的意思就是通过Ribbon调用microservice-provider-user服务时采用RandomRule(随机)的策略。
小结:消费者调用服务者时,我们使用Ribbon实现请求的负载均衡,而且Eureka的jar中已经集成了Ribbon,不用再引jar包即可直接使用。使用有三种方法:1.直接在RestTemplate实例中添加@LoadBalanced即可实现默认轮询的负载均衡,2.自定义负载均衡策略类,并在RibbonClient中引入这个策略类,3.在yml配置文件中自定义负载策略,其中第三种比较方便。

2.使用Ribbon不使用Eureka

为防止污染其它代码,再创建一个microservice-consumer-movie-ribbon-without-eureka项目,用来演示使用Ribbon时不适用Eureka。
官方文档中在application.yml中这样配置:

1
2
3
stores:
ribbon:
listOfServers: example.com,google.com

意为通过ribbon调用stores不适用Eureka中注册的服务者,而是调用example.com和google.com这两个服务。
那我也这样做,配置文件如下:

1
2
3
microservice-provider-user:
ribbon:
listOfServers: localhost:7100

当使用ribbon调用microservice-provider-user服务时调用localhost:7100的服务,虽然Eureka中microservice-provider-user注册了好几个服务提供者(比如localhost:7100,localhost:7101,localhost:7102),但只会调用localhost:7100,这就是使用Ribbon不使用Eureka。注意:@LoadBalanced注解不要落下,因为listOfServers这里也可以配置多个主机,同样需要负载均衡。
文档地址:https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-ribbon-without-eureka

3.在Ribbon中禁用Eureka

application.yml中这样配置即可:

1
2
3
ribbon:
eureka:
enabled: false

文档地址:https://springcloud.cc/spring-cloud-dalston.html#_example_disable_eureka_use_in_ribbon

4.声明式的Rest客户端:Feign

Feign是一种声明式的Web服务客户端,通过它可以方便的调用服务提供者的服务。使用Feign的步骤如下:

  • 引入Feign的jar

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>
  • 自定义Feign客户端类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * @Description 用户自定义Feign客户端
    * @Author 刘俊重
    * @Date 2017/11/8
    */
    @FeignClient("microservice-provider-user")
    public interface UserFeign {

    @RequestMapping(value = "/simple/{id}",method = RequestMethod.GET)
    public User findUserbyId(@PathVariable("id") Long id);
    }

@FeignClient注明要调用的服务microservice-provider-user,public User findUserbyId(@PathVariable(“id”) Long id)这个接口是服务提供者接受调用的。

  • Main方法中加入@EnableFeignClients表明这个Feign客户端

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    public class MicroserviceConsumerMovieFeignApplication {
    public static void main(String[] args) { SpringApplication.run(MicroserviceConsumerMovieFeignApplication.class, args);
    }
    }
  • 消费者调用端注入自定义的Feign客户端,发起调用请求。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    public class MovieController {
    @Autowired
    private UserFeign userFeign;
    @GetMapping("/simple/{id}")
    public User findUserById(@PathVariable Long id){
    return userFeign.findUserbyId(id);
    }
    }

自定义了Feign客户端就不需要再使用RestTemplate调用了,现在的调用顺序是调用者通过调用自定义Feign客户端调用,自定义的Feign客户端又通过@FeignClient声明要调用的服务,通过跟服务者同名的方法调用。但是这里还有几个坑:

  1. @GetMapping不支持,需要使用@RequestMapping,在括号里面指定请求方式。
  2. @PathVariable得设置value,@PathVariable(“id”)如果没有(“id”)会报错
  3. Feign里面方法的参数如果是复杂对象(引用数据类型),即使你通过method = RequestMethod.GET指定这个get请求,请求到达服务提供者时依然是以Post请求方式到达的。

    5.覆盖Feign的默认值(自定义Feign的配置方式)

    默认情况下,Feign使用的是SpringMvcContract(Springmvc的契约),可以在Feign客户端中使用
    1
    @RequestMapping(value = "/simple/{id}",method = RequestMethod.GET)

这种方式调用服务者,但是假如我们不想采用这种方式,而是通过自定义的方式那就可以定义这样一个类。

  • 定义Feign配置类,覆盖默认配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * @author 刘俊重
    * @Description 自定义Feign的配置
    */
    @Configuration
    public class CustomFeignConfiguration {
    @Bean
    public Contract feignContract() {
    return new feign.Contract.Default();
    }
    }
  • 在Feign客户端中引入刚才定义的配置类

    1
    2
    3
    4
    5
    @FeignClient(name="microservice-provider-user",configuration = CustomFeignConfiguration.class)
    public interface UserFeign {
    @RequestLine("GET /simple/{id}")
    public User findUserbyId(@Param("id") Long id);
    }

因为我们已经覆盖了Feign的默认配置,自定义了CustomFeignConfiguration,里面定义契约不再采用springmvc的而是采用Feign的,所以这里请求注解@RequestMapping要改成@RequestLine,相应参数也要改变了,不然会报错。参考文档:https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-feign-overriding-defaults

6.解决Feign第一次请求超时的问题

在实际测试中发现如果机器配置比较低,第一次使用Feign调用服务者时经常会出现超时的现象,之后再调用就没有问题了,可以通过以下方法解决。

1
2
3
4
5
6
# 解决第一次请求报超时异常的方案:
# hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 (将请求超时时间设为5秒)
# 或者: (直接禁用超时提醒)
# hystrix.command.default.execution.timeout.enabled: false
# 或者:
feign.hystrix.enabled: false ## 索性禁用feign的hystrix支持

Eureka的高可用设置

Eureka在这里充当的是注册中心的作用,所以必须要保证高可用,每个Eureka服务器同时也是一个Eureka客户端,同时还需要至少一个服务来定位对等体。Eureka的高可用可以采用下面的步骤来完成。

  • application.xml中配置多个Eureka对等体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    spring:
    application:
    name: EUREKA-HA
    ---
    server:
    port: 8761
    spring:
    profiles: peer1
    eureka:
    instance:
    hostname: peer1
    client:
    serviceUrl:
    defaultZone: http://peer2:8762/eureka/
    ---
    server:
    port: 8762
    spring:
    profiles: peer2
    eureka:
    instance:
    hostname: peer2
    client:
    serviceUrl:
    defaultZone: http://peer1:8761/eureka/

上面的意思就是定义一个应用名为EUREKA-HA,定义一个profiles名为peer1,它的主机名是peer1,将其注册到http://peer2:8762/eureka/ 服务器上,peer2跟它同理,现在peer1跟peer2就是对等体(名字可以随便定)。

  • window系统修改hosts文件。进入“C:\Windows\System32\drivers\etc”,修改hosts文件

    1
    127.0.0.1 peer1 peer2
  • 依次启动peer1和peer2。
    启动pee1时可能会报错,因为它要把服务注册到peer2上,但是peer2还未启动,不用管它,访问http://localhost:8761 ,发现什么都没有;接着启动peer2,访问http://localhost:8762 ,发现8761和8762都注册在了8762上,此时在刷新8761的浏览器页面发现8761和8762已经都在了,因为peer1和peer2是对等体,他们互相注册,服务注册表也是共享了,由此就组成了Eureka的高可用。启动peer1和peer2图片如下图(Active profiles必须配置,否则报错):
    这里写图片描述
    注意:如果你引入了spring-boot-starter-security包,但是并没有指定用户名和密码,那么Eureka会为你生成一个随机密码,导致你登录不上,所以请不要引入spring-boot-starter-security或者自己指定密码。参考地址:https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-eureka-server-zones-and-regions
    总结:本节首先是将服务提供者和消费者注册在了Eureka的注册中心上,之后消费者通过两种方式调用服务者,一种是Ribbon负载均衡调用,一种是用Feign客户端调用,当然在每种调用方式中都可以进行自定义配置;鉴于Eureka注册中心的地位非常重要,一旦宕机将导致其它服务都不可用,所以需要保证Eureka注册中心的高可用。
    代码地址:https://gitee.com/catchu/springcloud-study

刘俊重 wechat
欢迎关注我的微信公众号
坚持原创技术分享