SpringCloud.Feign Hystrix

 消费者声明式调用/降级/负载均衡(Feign)。

介绍

Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
Feign调用的是对方的Rest接口,所以这里只需要对服务消费方做处理,整合的时候添加对Feign的依赖和支持也只是在消费方处理,服务方不需要做任何处理,只需要将接口注册到Eureka即可。

整合

服务消费方工程添加。

POM

新增:


	org.springframework.cloud
	spring-cloud-starter-openfeign

完整:



	4.0.0

	com.example
	boot1
	0.0.1-SNAPSHOT
	jar

	demo1

	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.6.RELEASE
		 
	

	
		UTF-8
		UTF-8
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-ribbon
		

		
			org.springframework.cloud
			spring-cloud-starter-netflix-hystrix
		

		
			org.springframework.cloud
			spring-cloud-starter-openfeign
		

	

	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				Finchley.SR2
				pom
				import
			
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

入口类

@EnableFeignClients:开启Spring Cloud Feign的支持功能

代码:下面代码是在整合了Hystrix和Robin基础的入口类代码,如果单独做示例可以单独添加@EnableFeignClients即可。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient //这里也要用到@EnableDiscoveryClient, 让服务使用eureka服务器, 实现服务注册和发现
@EnableHystrix
@EnableFeignClients //开启Feign
@SpringBootApplication
public class BootApplication {

    //@Bean 应用在方法上,用来将方法返回值设为为bean
    //@LoadBalanced实现负载均衡
    @Bean
    @LoadBalanced  
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

HelloWord

最小示例代码。

包括:服务提供者Server,服务消费者Client。其中Server不需要对外特殊处理,就是普通的Rest接口。需要处理的消费者端。这里的Server.Controller只是为了测试使用,并不表示也需要对此做改动。

Server.Controller

服务提供者名称:boot1 (这是在工程中配置的)

服务接口本地测试URL:http://localhost:9091/boot1/fun1

代码:

@RestController
public class Test1Controller {
    
    private final Logger log = LoggerFactory.getLogger(Test1Controller.class);
    
    @RequestMapping("/fun1")
    public Test1Model1 fun1(){
        log.debug("fun1 .....");
        return new Test1Model1("id1","name1");
    }
}

Client.Service

@FeignClient(“hello-service”):制定服务名来绑定服务、

注:服务名不区分大小写

@RequestMapping:指定调用服务中的具体方法URI,包含项目名称

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo1.entity.Test1Model1;

@FeignClient("boot1") //服务名,不是项目名称
public interface Test1Service {
    
    @RequestMapping("/boot1/fun1") //访问路径名,如果有项目名称,也需要带着
    public Test1Model1 fun1();

}

  Client.Controller

  1. 正常通过@Autowired注入Service
  2. 调用跟调用本地Service一样
  3. 依赖的entity如果是服务提供者的,可以通过jar包依赖进来,或者自己写一个实体
@RestController
public class Test1Controller2 {
    
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private Test1Service test1Service;
    
    @RequestMapping("/hello2")
    public String hello() {
        Test1Model1 m = this.test1Service.fun1();
        log.info("hello result,{}",m.toString());
        return "hello consumer finish !!!" + m.toString();
    }
}

验证

http://localhost:9092/boot2/hello2

hello consumer finish !!!Test1Model1 [id=id1, name=name1]

boot1日志:fun1 .....

传参的多种方式

@RequestParam、@RequestHeader、@RequestBody 三种传值方式,按照名称可以看出为:参数方式 Get请求,Header方法 Get请求,Body方式 POST请求。

注意:各参数绑定时@RequestParam、@RequestHeader等可以指定参数名称的注解,但它们的value不能少,否则会报错,这和SpringMVC中有一点不同。

Serer.Controller

访问地址:http://localhost:9091/boot1/hello1

注册中心服务:boot1

@RequestMapping(value = "/hello1", method = RequestMethod.GET)
    @ResponseBody
    public String hello(@RequestParam String name) {
        return "hello " + name;
    }

    @RequestMapping(value = "/hello2", method = RequestMethod.GET)
    @ResponseBody
    public User hello(@RequestHeader String name, @RequestHeader Integer age) {
        return new User(name, age);
    }

    @RequestMapping(value = "/hello3", method = RequestMethod.POST)
    @ResponseBody
    public String hello(@RequestBody User user) {
        return "Hello " + user.getName() + ", " + user.getAge();
    }

Client.Service

@FeignClient 服务名

@RequestParam@RequestHeader@RequestBody 对应Server.Controller中即可

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.demo1.entity.User;

@FeignClient("boot1")
public interface UserService {
    
    @RequestMapping(value = "/boot1/hello1", method = RequestMethod.GET)
    String hello(@RequestParam("name") String name);

    @RequestMapping(value = "/boot1/hello2", method = RequestMethod.GET)
    User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

    @RequestMapping(value = "/boot1/hello3", method = RequestMethod.POST)
    String hello(@RequestBody User user);
    
}

Client.Controller

@RestController
public class Test1Controller3 {
    
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/hello3")
    public String hello() {
        log.info("--->"+userService.hello("dayday"));
        log.info("--->"+userService.hello("dayday",18).toString());
        log.info("--->"+userService.hello(new User("dayday",19)));
        return "success";
    }
}

验证

http://localhost:9092/boot2/hello3

返回:success

日志:

========                            

 --->hello dayday                     

 --->User{name='dayday', age=18}      

 --->Hello dayday, 19                 

 整合Hystrix进行降级

此方案是在6.3的基础来完成的。

开启feign.hystrix

添加配置 application.properties

##开启feign开关

feign.hystrix.enabled=true

 添加降级方法

该类实现了 接口。如果能调用同就调用API,如果调用不同则使用该做做降级。

import org.springframework.stereotype.Component;

import com.example.demo1.entity.Test1Model1;
import com.example.demo1.service.Test1Service;

@Component //让spring管理
public class Test1ServiceFallback  implements  Test1Service{
    @Override
    public Test1Model1 fun1() {
        return new Test1Model1("501","降级返回的数据");
    }
}

降级方法同Client.Service接口关联

@FeignClient(name="boot1",fallback = Test1ServiceFallback.class)

说明:name:服务名,不是项目名称;fallback为降级类,降级类需要实现此接口

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo1.entity.Test1Model1;
import com.example.demo1.service.fallback.Test1ServiceFallback;

//@FeignClient("boot1") //服务名,不是项目名称
//name:服务名,不是项目名称;fallback为降级类,降级类需要实现此接口
@FeignClient(name="boot1",fallback = Test1ServiceFallback.class) 
public interface Test1Service {
    @RequestMapping("/boot1/fun1") //访问路径名,如果有项目名称,也需要带着
    public Test1Model1 fun1();
}

Controller回顾

    @RequestMapping("/hello2")
    public String hello() {
        Test1Model1 m = this.test1Service.fun1();
        log.info("hello result,{}",m.toString());
        return "hello consumer finish !!!" + m.toString();
    }

验证

调用:http://localhost:9092/boot2/hello2

结果:hello consumer finish !!!Test1Model1 [id=501, name=降级返回的数据]

拦截器RequestInterceptor

Feign是服务时间调用的组件,但在组件调用前后都会有一些操作,比如给调用的HTTP请求添加Header认证信息。因为服务提供者都是做用户的校验的,所以拦截器是在Feign-Client端的。

实现接口feign.RequestInterceptor,并添加@Configuration注解,类处于的位置需要被扫描到。

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig  implements RequestInterceptor{
    
    @Override
    public void apply(RequestTemplate requestTemplate){
        requestTemplate.header("Authorization", "BearereyJhbGciOiJIUzUx*****");
        requestTemplate.header("Accept", "application/json");
        requestTemplate.header("Content-Type", "application/json");
    }
}

就相当于在HTTP.Header部分添加了三部分。

Accept: application/json
Content-Type: application/json;charset=UTF-8
Authorization:Bearer Token值

消费者调用加入熔断机制(Hystrix)

熔断:防御;保证整体可用,不保证单次调用可用。

Hystrix介绍

Hystrix是原来就有的组件,不是SpringCloud自己实现的。SpringCloud只是用Hystrix来实现熔断机制,一般在集成SpringBoot使用的时候,都使用FeignFeign是整合了HystrixRibbon的组件,使用“声明式的REST客户端”调用方式,客户端负载均衡,调用不通情况下使用Hystrix做熔断保证整体系统的稳定性。

名字来源

hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo

SpringCloud.Feign Hystrix_第1张图片

What Is Hystrix?

在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。

熔断机制

断路器

断路器 在电路上就是保险丝,当电流超过阈(yu)值,保险丝就会熔断,断电。保险丝只是保证电流过大时损坏其他电路或者电器,并不保证电器可用。

分布式熔断必要性

在微服务中,单个服务为了高可用性防止单点故障通常会集群部署。由于网路或其他原因服务变得不可用时服务调用者会出现长等待的线程阻塞,此时会有大量的其他请求涌入,servlet容器线程资源会被消耗完毕。服务之间有依赖性,于是会出现故障传播引起雪崩效应,断路器就是为了解决这样的问题。

在服务与用户之间通过api进行交互,用户调用api消费服务,当某个服务不可用(有一个时间阈值)时断路器就会打开,同时为这个服务的调用者返回一个固定的值。简单来说就是为服务的瘫痪做一个保险,防止用户得不到服务返回结果而阻塞等待进而影响其他服务和用户。

​​​​​​​Hystrix的熔断机制

Hystrix主要通过以下几点实现延迟和容错:

包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。

跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。

资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。

监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。

回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。

自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,上文已经详细探讨过,不再赘述。

HelloWord

POM

添加 hystrix 的依赖


   org.springframework.cloud
   spring-cloud-starter-netflix-hystrix

完整:



	4.0.0

	com.example
	boot1
	0.0.1-SNAPSHOT
	jar

	demo1

	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.6.RELEASE
		 
	

	
		UTF-8
		UTF-8
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-ribbon
		

		
			org.springframework.cloud
			spring-cloud-starter-netflix-hystrix
		
		
	

	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				Finchley.SR2
				pom
				import
			
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

 入口类

在启动类上添加 @EnableCircuitBreaker 或 @EnableHystrix 注解,从而为项目启用断路器支持

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient 
@EnableHystrix
@SpringBootApplication
public class BootApplication {

    //@Bean 应用在方法上,用来将方法返回值设为为bean
    //@LoadBalanced实现负载均衡
    @Bean
    @LoadBalanced  
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

Controller

  1. 添加容错方法fun1Fallback
  2. 在调用方法外添加:@HystrixCommand(fallbackMethod = "fun1Fallback"),标注fallbackMethod为容错方法。
  3. 容错方法可以有参数,这个参数雷同Controller方法的参数,以及获取方法。
  4. 运行:如果hello方法调用超时等问题,就会调用fallbackMethod指定的方法
@RestController
public class Test1Controller {
    
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    //这里注入的restTemplate就是在com.sam.ConsumerApp中通过@Bean配置的实例
    @Autowired
    RestTemplate restTemplate;
    
    @RequestMapping("/hello/{id1}")
    @HystrixCommand(fallbackMethod = "fun1Fallback")
    public String hello(@PathVariable String id1) {
        //调用hello-service服务,注意这里用的是服务名,而不是具体的ip+port
        Test1Model1 m = restTemplate.getForObject("http://boot1/boot1/fun1", Test1Model1.class);
        log.info("hello result,{}",m.toString());
        return "hello consumer finish !!!" + m.toString();
    }
    
    /**
     * 配置fun1的fallback方法
     * @param id1 路径参数
     * @return
     */
    public String fun1Fallback(String id1) {
        log.warn("请求异常,执行回退方式,id1:{}",id1);
        Test1Model1 t = new Test1Model1();
        t.setId("201");
        t.setName("请求异常,执行回退方式");
        return t.toString();
    }
}

验证

调用:http://localhost:9092/boot2/hello/44444

正常:

日志:fun1 .....

结果:hello consumer finish !!!Test1Model1 [id=id1, name=name1]

异常:

日志:请求异常,执行回退方式,id1:44444

结果:Test1Model1 [id=201, name=请求异常,执行回退方式]


参考

Hystrix 其他配置

Hystrix断路器的状态监控

Hystrix线程隔离策略与传播上下文1

Hystrix线程隔离策略与传播上下文2

你可能感兴趣的:(WEB,Java,SpringCloud,Feign,Hystrix)