feign负载均衡和Hystix熔断器
feign负载均衡--------------
feign 是基于Ribbon负载均衡的实现,Feign对Ribbon进行了封装。
Feign执行原理
集成fegin
1)导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2)创建Feign的客户端 接口 也就是类 (看自己业务需求是否要单独提取出来做一个工程)
@FeignClient("user-service") //声明这是一个Feign客户端, Eureka中的名称
public interface UserFeignApi {
@GetMapping("/user/{id}") // 括号里面是service的地址 从Eureka里面获得url和port
public User findById(@PathVariable("id") String username);
}
那么如果消费者不止一个,每个消费者都需要去编写FeignClient接口,显然是对人力资源的浪费。
3)在consumer工程中新建FeignController,使用userFeignApi访问:
@RestController
@RequestMapping("feign")
public class FeignController {
@Autowired
UserFeignApi userFeignApi;
@GetMapping("{id}")
public User queryById(@PathVariable("id") String id){
return userFeignApi.findById(id);
}
}
4)在consumer启动类开启Feign功能
@SpringCloudApplication
@EnableFeignClients(basePackages = "com.itheima.sh.client")// 开启Feign功能
public class ConsumerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
访问 localhost:8081/feign/id
正确的做法应该是:
定义一个独立的jar工程
在该工程中,编写FeignClient接口及所需要的实体类
任何消费者,只需要引入这个jar包就能直接使用。
接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
注意:Feign中已经自动集成了Ribbon负载均衡,因此我们不需要自己定义RestTemplate了
后面开启@EnableFeginClients会用另一种方式书写
请求时长和请求机制 -yml
因为是封装了ribbon所以他有负载均衡的能力 他的依赖里面也有ribbon
Fegin内置的ribbon默认设置了请求超时时长,默认是1000ms
,我们可以通过手动配置来修改这个超时时长
ribbon:
ReadTimeout: 2000 # 读取超时时长
ConnectTimeout: 1000 # 建立链接的超时时长
因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试,可以添加配置:
ribbon:
ConnectTimeout: 500 # 连接超时时长
ReadTimeout: 1000 # 数据通信超时时长
MaxAutoRetriesNextServer: 2 # 切换重试多少次服务
MaxAutoRetries: 1 # 当前服务器的重试次数
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
配置fegin日志级别
前面讲过,通过
logging.level.xx=debug
来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient
注解修饰的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。
1)设置com.itheima.sh包下的日志级别都为debug 谁调用的它在睡得下面配置yml文件
logging:
level:
com.itheima.sh: debug
2)编写配置类,定义日志级别 --fegin配置日志是需要写一个类
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
Hystix熔断器--------------
分布式系统面临的问题 :扇出、服务雪崩
扇出
多个微服务互相调用的时候,如果A调用B、C,而B、C又继续调用其他微服务,这就是扇出(像一把扇子一样慢慢打开)。
服务雪崩
例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
雪崩效应产生的几种场景:
流量激增:比如异常流量、用户重试导致系统负载升高;
缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
硬件故障:比如宕机,机房断电,光纤被挖断等。
数据库严重瓶颈,比如:长事务、sql超时等。
线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;
雪崩解决方案
超时机制
通过网络请求其他服务时,都必须设置超时。正常情况下,一个远程调用一般在几十毫秒内就返回了。当依赖的服务不可用,或者因为网络问题,响应时间将会变得很长(几十秒)。而通常情况下,一次远程调用对应了一个线程/进程,如果响应太慢,那这个线程/进程就会得不到释放。而线程/进程都对应了系统资源,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。所以必须为每个请求设置超时。
断路器模式
试想一下,家庭里如果没有断路器,电流过载了(例如功率过大、短路等),电路不断开,电路就会升温,甚至是烧断电路、起火。有了断路器之后,当电流过载时,会自动切断电路(跳闸),从而保护了整条电路与家庭的安全。当电流过载的问题被解决后,只要将关闭断路器,电路就又可以工作了。
同样的道理,当依赖的服务有大量超时时,再让新的请求去访问已经没有太大意义,只会无谓的消耗现有资源。譬如我们设置了超时时间为1秒,如果短时间内有大量的请求(譬如50个)在1秒内都得不到响应,就往往意味着异常。此时就没有必要让更多的请求去访问这个依赖了,我们应该使用断路器避免资源浪费。
断路器可以实现快速失败,如果它在一段时间内侦测到许多类似的错误(譬如超时),就会强迫其以后的多个调用快速失败,不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费CPU时间去等待长时间的超时。断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
断路器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
Hystix 解决雪崩问题(概念)
Hystrix是一个用于处理分布式系统延迟和容错的开源库。分布式系统中,依赖避免不了调用失败,比如超时,异常等。Hystrix能保证在出现问题的时候,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
hystix作用
Hystrix主要的作用就是:服务的熔断、服务降级、服务限流、近实时监控。
Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
服务的熔断(保险丝)
- 熔断机制的注解是==@HystrixCommand==
- 熔断机制是应对雪崩效应的一种==链路保护机制==,一般存在于服务端
- 当扇出链路的某个服务出现故障或响应超时,会进行==服务降级,进而熔断该节点的服务调用==,快速返回“错误”的相应信息。
Hystrix的熔断存在阈值,缺省是5秒内20次调用失败就会触发
线程隔离,服务降级
线程隔离:
Hystrix为每个服务调用的功能分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理:返回给用户一个错误提示或备选结果。
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级
服务降级**虽然会导致请求失败,但是不会导致阻塞,而且最多占用该服务的线程资源,不会导致整个容器资源耗尽,把故障的影响隔离在线程池内。
Hystrix降级demo实现
线程隔离的完整步骤包括:
- 引入Hystrix的依赖
- 添加注解,开启Hystix功能
- 给业务编写降级的备用处理逻辑
- 给业务添加注解,开启线程隔离功能
- 设置触发降级的最长等待时间
现在,consumer-demo是服务的调用者,user-service是服务的提供者,因此consumer-demo需要把调用user-service的业务隔离,避免级联失败。
因此我们接下来的操作都是在consumer-demo中添加的。
1)在consumer-demo添加Hystix的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)开启熔断 在consumer-demo的启动类上添加注解:@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ConsumerApplication {
// ...
}
我们这里可以使用@SpringCloudApplication这个注解代替上面三个注解以为它包含了上面三个注解
3)编写降级业务(这个demo只适用于只有一个方法,大批量的话每个方法都要指定太麻烦,后面另一种Hystrix结合Feign ,工作中会用到第二种)
当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,然后使用HystixCommond来指定降级的方法。
consumer-demo中ConsumerController在queryById方法上添加 @HystrixCommand注解用来声明一个降级逻辑的方法
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallBack")
public User queryById(@PathVariable("id") String id){
User user = restTemplate.getForObject("http://user-service/user/"+id, User.class);
return user;
}
// 降级方法
public User queryByIdFallBack(String id) {
User user = new User();
user.setName("暂停服务");
return user;
}
注意:相同的参数列表和返回值声明**。失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以我们把queryById的方法改造为返回String,反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。
超时设置 --yml
这里的超时时间要大于Fegin的请求时长和建立连接时常的总和
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 2000
熔断器触发的阀值 – yml
熔断器的默认触发阈值是20次请求,不好触发。休眠时间时5秒,时间太短,不易观察,为了测试方便,我们可以通过配置修改熔断策略:
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 2000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
服务熔断 状态机模型
Hystrix熔断状态机的模型
状态机有3个状态:
- Closed:关闭状态(断路器关闭),所有请求都正常访问。
- Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
Feign集成Hystix
Fe0gn默认也有对Hystix的集成,只不过,默认情况下是关闭的。我们需要通过下面的参数来开启
步骤:
1.在Fegin 的yml文件中开启 熔断功能
2.定义一个类返回一个降级之后传给前端的友好页面 UserFeignApiFallBack(随意定义)
3.然后在Fegin(FeignController)的客户端中,指定刚才编写的实现类
1)在Fegin 的yml文件中开启 熔断功能
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
2)首先,我们要定义一个类,实现刚才编写的UserFeignClient,作为fallback的处理类
package com.itheima.sh.clients.fallback;
import com.itheima.sh.clients.UserFeignApi;
import com.itheima.sh.pojo.User;
import org.springframework.stereotype.Component;
@Component
public class UserFeignApiFallBack implements UserFeignApi {
@Override
public User findById(String username) {
User user = new User();
user.setName(username);
user.setNickName("UserFeignApiFallBack");
return user;
}
}
3)然后在Fegin(FeignController)的客户端中,指定刚才编写的实现类
fallback = UserFeignApiFallBack.class 这个就是用来指定的
package com.itheima.sh.client;
import com.itheima.sh.client.fallback.UserFeignApiFallBack;
import com.itheima.sh.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @program: Fegin的客户端
* @description:
* @author: Mr.Wang
* @create: 2020-11-27 13:04
**/
@FeignClient(value = "USER-SERVICE",fallback = UserFeignApiFallBack.class) //指定刚刚编写的实现类
public interface FeignController {
@GetMapping("/user/{username}")
public User findById(@PathVariable("username")String name);
}