Springboot 整合 SpringCloud组件-Feign(Ribbon/Hystrix) (三)

妖狐艹你老母 2021-12-14 14:05 398阅读 0赞

直到上一篇https://blog.csdn.net/qq_35387940/article/details/94562280,咱们已经把Eureka Server注册中心搭建完毕,也把微服务成功注册到了注册中心里去了。

接下来,我们整合的是,非常核心实用的组件-Feign,

Feign除了解决了微服务之间调用,里面还囊括了 Ribbon负载均衡以及Hystrix 熔断降级。

Ribbon 负载均衡:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70

Hystrix 熔断降级:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 1

同样,创建一个springboot项目,起名feign作为一个微服务:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 2

(同样,我们这里选用的springcloud版本是:Finchley.RELEASE)

既然作为一个微服务,那自然也是需要注册到注册中心去的,所以pom.xml里核心的依赖包为:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.cloud</groupId>
  11. <artifactId>spring-cloud-starter-openfeign</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.cloud</groupId>
  15. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
  20. </dependency>

application.yml:

  1. eureka:
  2. instance:
  3. preferIpAddress: true
  4. instance-id: ${spring.cloud.client.ip-address}:${server.port}
  5. client:
  6. serviceUrl:
  7. defaultZone: http://localhost:8761/eureka/
  8. server:
  9. port: 8765
  10. spring:
  11. application:
  12. name: feign
  13. feign:
  14. hystrix:
  15. enabled: true

(fegin: hystrix : enabled : true 这个配置项是指,开启熔断机制,也许在yml里显示没有相关依赖,但是不用慌,也许是因为版本问题,这个设置项加上就行,是起作用的)

然后我们在启动类上加上各种注解:

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  4. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  5. import org.springframework.cloud.netflix.hystrix.EnableHystrix;
  6. import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
  7. import org.springframework.cloud.openfeign.EnableFeignClients;
  8. @SpringBootApplication
  9. @EnableEurekaClient
  10. @EnableDiscoveryClient
  11. @EnableHystrix
  12. @EnableHystrixDashboard
  13. @EnableFeignClients
  14. public class FeignApplication {
  15. public static void main(String[] args) {
  16. SpringApplication.run(FeignApplication.class, args);
  17. }
  18. }

OK,基本的条条框框,我们已经做完了,接下来进行 使用Feign去调用我们之前创建的微服务client-test。

我们来看看,我们准备调用的client-test里面的接口:

  1. @Value("${server.port}")
  2. String port;
  3. @RequestMapping("/haveatry")
  4. public String home(@RequestParam(value = "name", defaultValue = "forezp") String name) {
  5. return "微服务 client-test被调用, " + "name为:"+name + " ,被调用的服务端口 port:" + port;
  6. }

那么我们在feign服务里,要这么写,才能成功调用:

先创建一个interface ,SchedualServiceHi.java:

  1. import com.cloud.feign.service.impl.SchedualServiceHiHystric;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. /**
  8. * @Author:JCccc
  9. * @Description:
  10. * @Date: created in 14:42 2019/2/3
  11. */
  12. @FeignClient(value = "client-test",fallback = SchedualServiceHiHystric.class)
  13. @Component
  14. public interface SchedualServiceHi {
  15. @RequestMapping(value = "/haveatry",method = RequestMethod.GET)
  16. String sayHiFromClientOne(@RequestParam(value = "name") String name);
  17. }

可以看到,

  1. 需要保证@FeignClient 里面,value的值是 调用的接口所属微服务名称;

2.需要保证请求的Url是一致的;

3.需要保证接口请求方式是一致的;

4.需要保证接口传参是一致的;

5.需要使用@RequestMapping这种方式(避免使用GetMapping/PostMapping)

6.额外提醒,只要是传参,请都加上@RequestParam(”XXXX”);如果传对象就加上@RequestBody; 这样你就不用踩Feign的传参的坑了,不管是使用Feign的服务还是提供接口的client服务,请都加上这些传参注解保持一致;

看的仔细地人已经看到了fallback后面的类,这个类就是用于熔断降级的,SchedualServiceHiHystric.java:

  1. import com.cloud.feign.service.SchedualServiceHi;
  2. import org.springframework.stereotype.Component;
  3. /**
  4. * @Author:JCccc
  5. * @Description:
  6. * @Date: created in 14:48 2018/2/3
  7. */
  8. @Component
  9. public class SchedualServiceHiHystric implements SchedualServiceHi {
  10. @Override
  11. public String sayHiFromClientOne(String name) {
  12. return "sorry! 网络异常,服务暂时无法访问。 请求的name为:"+name;
  13. }
  14. }

OK,到这里,我们其实已经打通了跨服务直接的接口调用,那么我们写一个属于feign服务的接口(因为feign也是一个微服务已经注册到了注册中心,跨服务调用接口直接因为我们分布式架构按照业务把服务拆分了,业务需求需要从feign服务这里的接口调用到client-test服务接口,所以就需要用到了Feign组件):

HiController.java:

  1. import com.cloud.feign.service.SchedualServiceHi;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestParam;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * @Author:JCccc
  8. * @Description:
  9. * @Date: created in 14:44 2019/2/3
  10. */
  11. @RestController
  12. public class HiController {
  13. @Autowired
  14. SchedualServiceHi schedualServiceHi;
  15. @GetMapping(value = "/feignTest")
  16. public String sayHi(@RequestParam String name) {
  17. return schedualServiceHi.sayHiFromClientOne(name);
  18. }
  19. }

好,现在我们先将项目跑起来(前提注册中心以及client-test也是正在运行的),看看跨服务调用效果(熔断和负载均衡一会略详细讲讲,不慌):

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 3

我们调下feign的接口 http://localhost:8765/feignTest?name=1323 :

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 4

可以看到,跨服务间调用已经成功。

然后,下面我们直接把 client-test服务关掉(狠心),feign的接口 http://localhost:8765/feignTest?name=1323 :

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 5

看到返回值,显示的内容, 没错就是我们刚刚配置的fallback类:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 6

这里可以根据使用场景,扩展做数据存储、降级调用等等。

到这里,基本feign的使用也七七八八, 就剩一个负载均衡的使用:

那么我们继续, 既然是负载均衡,那么也先得有东西均衡,所以我们直接再创建一个微服务,这个微服务跟上一篇的client-test微服务一模一样,依赖、配置全都保持一样, 唯一的改变是 端口!

上一篇的client-test微服务端口是8762, 我们这个是8763(注意,服务名保持一样 client-test,因为负载均衡是通过获取注册中心的服务注册信息,根据服务名去匹配的):

  1. server:
  2. port: 8763
  3. spring:
  4. application:
  5. name: client-test
  6. zipkin:
  7. base-url: http://localhost:9411
  8. sender:
  9. type: web
  10. sleuth:
  11. sampler:
  12. probability: 1.0
  13. eureka:
  14. #以IP地址方式显示在注册中心
  15. instance:
  16. preferIpAddress: true
  17. instance-id: ${spring.cloud.client.ip-address}:${server.port}
  18. client:
  19. serviceUrl:
  20. defaultZone: http://localhost:8761/eureka/

然后也将这个新的client-test微服务跑起来,先访问注册中心看看情况 http://localhost:8761/:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 7

OK,负载均衡的准备已经完成,那么我们连续调下feign的接口 http://localhost:8765/feignTest?name=1323 :

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 8

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 9

从调用的返回值我们可以得知,已经负载均衡了,默认的负载机制是轮换地调用,我们只需横向扩展需要实现负载均衡的服务实例。

到了这里,讲道理是已经整合完毕了,但是,也许细心思考的人会有以下的想法:

又熔断降级、又负载均衡,那么如果现在有3个client-test服务实例, 其中有一个挂掉或者访问响应比较慢,那么负载和均衡直接是怎么样的处理结果呢?

接下来我们用client-test服务实例 来简单模拟下场景,

首先我们刚刚试过,2个client-test服务实例 都正常访问,响应都是正常的时候, 不可能出现熔断降级,然后负载均衡是,轮流一次次地访问到了这两个实例;

OK,我们在8762这个服务实例的接口上,加上一个sleep(5000):

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1Mzg3OTQw_size_16_color_FFFFFF_t_70 10

然后连续调下feign的接口 http://localhost:8765/feignTest?name=1323 :

结果是优先负载均衡轮流调用实例,调用到正常运行的服务则正常调用;若调用到时间久的,就会出现熔断降级。

那么这种情况,其实我们是可以设置feign调用服务的连接超时时间的,

我们创建一个FeignConfiugure.java:

  1. import feign.Request;
  2. import feign.Retryer;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. /**
  6. * @Author:JCccc
  7. * @Description:
  8. * @Date: created in 10:56 2019/2/3
  9. */
  10. @Configuration
  11. public class FeignConfigure {
  12. public static int connectTimeOutMillis = 7000;//超时时间
  13. public static int readTimeOutMillis =7000;
  14. @Bean
  15. public Request.Options options() {
  16. return new Request.Options(connectTimeOutMillis, readTimeOutMillis);
  17. }
  18. @Bean
  19. public Retryer feignRetryer() {
  20. return new Retryer.Default();
  21. }
  22. }

注意这里设置的其实是负载均衡的连接超时时间,熔断默认是1000ms,也就是说连接超过1000ms都直接熔断了,根本不会负载均衡。 所以在这里建议,负载均衡的超时时间设置最好是不要大于熔断超时时间, 这样才是符合逻辑的。

那么熔断时间怎么设置呢,

我们可以直接在yml设置(下面的设置项也许在yml文件里面也会显示不存在,但是其实是有效的):

  1. ### Hystrix 配置
  2. hystrix:
  3. # 这样将会自动配置一个 Hystrix 并发策略插件的 hook,这个 hook 会将 SecurityContext 从主线程传输到 Hystrix 的命令。
  4. # 因为 Hystrix 不允许注册多个 Hystrix 策略,所以可以声明 HystrixConcurrencyStrategy
  5. # 为一个 Spring bean 来实现扩展。Spring Cloud 会在 Spring 的上下文中查找你的实现,并将其包装在自己的插件中。
  6. shareSecurityContext: true
  7. command:
  8. default:
  9. circuitBreaker:
  10. # 当在配置时间窗口内达到此数量的失败后,进行短路。默认20个
  11. requestVolumeThreshold: 1
  12. # 触发短路的时间值,当该值设为5000时,则当触发 circuit break 后的5000毫秒内都会拒绝request
  13. # 也就是5000毫秒后才会关闭circuit。默认5000
  14. sleepWindowInMilliseconds: 15000
  15. # 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
  16. forceOpen: false
  17. # 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略,默认false
  18. forceClosed: false
  19. execution:
  20. isolation:
  21. thread:
  22. # 熔断器超时时间,默认:1000/毫秒
  23. timeoutInMilliseconds: 10000

我这边是故意设置熔断时间为10s,负载均衡时间为7秒, 然后接口sleep是5s,然后调用接口访问,就可以明显观察,不会存在熔断返回值只是访问特别久才有返回结果,因为sleep是5秒;

OK,两个时间咱们都可以设置,具体就根据业务需求去设置吧。

Springboot整合SpringCloud -Feign的详细介绍就到此。

以下是我自己设置各个时间后,调用接口的简述(可以无视,自己亲自去试试时间的设置搭配):

我们可以再把熔断时间改为4秒,负载均衡时间为7秒,然后接口sleep是5s,调用接口访问,可以看到直接触发了熔断效果;

我们可以再把熔断时间改为7秒,负载均衡时间为3秒,然后接口sleep是5s,调用接口访问,

可以看到当调用到超过负载时间的接口时,并没直接返回熔断结果(因为还没到熔断的7s),而是返回了正常没有设置sleep的另一个实例的接口返回值(因为这时候超过了负载的3秒,所以组件帮我们去正常的服务了)。

其实这种情况才是最符合我们使用场景的,也就是设置的熔断时间比负载均衡时间长就行了,

当服务调用进行负载,发现如果有的服务连接不上或者时间太久,就会去寻找能正常在负载均衡连接时间范围内访问的服务,去调用接口,然后如果一直都找不到能符合条件的,那么就是熔断场景。

发表评论

表情:
评论列表 (有 0 条评论,398人围观)

还没有评论,来说两句吧...

相关阅读