消费者声明式调用/降级/负载均衡(Feign)。
Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
Feign调用的是对方的Rest接口,所以这里只需要对服务消费方做处理,整合的时候添加对Feign的依赖和支持也只是在消费方处理,服务方不需要做任何处理,只需要将接口注册到Eureka即可。
服务消费方工程添加。
新增:
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);
}
}
最小示例代码。
包括:服务提供者Server,服务消费者Client。其中Server不需要对外特殊处理,就是普通的Rest接口。需要处理的消费者端。这里的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");
}
}
@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();
}
@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中有一点不同。
访问地址: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();
}
@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);
}
@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 |
此方案是在6.3的基础来完成的。
添加配置 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","降级返回的数据");
}
}
@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();
}
@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=降级返回的数据]
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是原来就有的组件,不是SpringCloud自己实现的。SpringCloud只是用Hystrix来实现熔断机制,一般在集成SpringBoot使用的时候,都使用Feign,Feign是整合了Hystrix和Ribbon的组件,使用“声明式的REST客户端”调用方式,客户端负载均衡,调用不通情况下使用Hystrix做熔断保证整体系统的稳定性。
hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo。
在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。
断路器 在电路上就是保险丝,当电流超过阈(yu)值,保险丝就会熔断,断电。保险丝只是保证电流过大时损坏其他电路或者电器,并不保证电器可用。
在微服务中,单个服务为了高可用性防止单点故障通常会集群部署。由于网路或其他原因服务变得不可用时服务调用者会出现长等待的线程阻塞,此时会有大量的其他请求涌入,servlet容器线程资源会被消耗完毕。服务之间有依赖性,于是会出现故障传播引起雪崩效应,断路器就是为了解决这样的问题。
在服务与用户之间通过api进行交互,用户调用api消费服务,当某个服务不可用(有一个时间阈值)时断路器就会打开,同时为这个服务的调用者返回一个固定的值。简单来说就是为服务的瘫痪做一个保险,防止用户得不到服务返回结果而阻塞等待进而影响其他服务和用户。
Hystrix主要通过以下几点实现延迟和容错:
包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,上文已经详细探讨过,不再赘述。
添加 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);
}
}
@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