接上篇文章:
https://www.jianshu.com/p/dac81a7bde30
负载均衡Ribbon
什么是Ribbon
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为,为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法、自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。我们也可以自定义Ribbon的负载均衡算法
实现Ribbon负载均衡
我们启动两个user-service实例,一个8081 一个8082
和Eureka的高可用配置方式一致,复制启动参数,修改启动端口
-Dserver.port=8082
EureKa监控面板
开启负载均衡
Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码
在RestTemplate的配置方法上添加@LoadBalanced注解
@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
修改调用方式,不再手动获取ip和端口,直接通过服务名称调用
@GetMapping("{id}")
public User queyById(@PathVariable("id") Long id) {
//根据服务id获取服务实例列表
String url = "http://user-service/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
源码分析
为什么在这里我们输入服务名称就可以调用了呢?之前还需要获取ip和端口
显然有人帮我们根据service名称,获取到了服务实例和ip端口。它就是LoadBalanceInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id
我们来看一下源码:
修改负载均衡规则的配置入口:consumer的微服务配置
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式:{服务名称}.ribbon.NFLoadBalancerRuleClassName 值就是IRule的实现类
负载均衡策略
Ribbon默认是采用懒加载,即第一次访问时才会去创建负载均衡客户端,往往会出现超时,如果需要采用饥饿加载,随项目启动创建,可以这样配置
ribbon:
eager-load:
clients: user-service
enabled: true
负载均衡的源码流程
通过RestTemplate进行请求时,我们访问的地址是http://user-service/user/1,RibbonLoadBalancerInterceptor会拦截RestTemplate的请求,并获取请求的host,然后根据服务id获取服务列表,然后会根据负载均衡规则选中某个服务,选中服务后会重写URL,这样就相当于通过ip和端口调用某个服务
Hystrix
简介
Hystrix是Netflix公司的一款组件,是一个开源的延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
雪崩问题
在微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口与才能实现,会形成非常复杂的调用链路:
在一次业务请求中,需要调用A、P、H、I四个服务,这四个服务又可能又调用其他服务,这个时候,如果某个服务出现异常
例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的请求进来后,就会造成越来越多的阻塞
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
Hystrix解决雪崩问题的手段主要是服务降级
- 线程隔离
- 服务熔断
线程隔离,服务降级
原理
线程隔离示意图
解读:
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理
服务降级
优先保证核心服务,而非核心服务不可用或弱可用
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统奔溃,至少可以看到一个执行结果。
服务降级虽然会导致请求失败,但是不会导致阻塞。而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有影响。
触发Hystrix服务降级的情况
- 线程池已满
- 请求超时
实战
引入依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
开启熔断
在启动类上添加注解 @EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端
@EnableCircuitBreaker //开启熔断
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
我们在类上的注解越来越多,其中 @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker 可以合并为一个注解
@SpringCloudApplication
编写降级逻辑
当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。这样我们就需要提前编写好失败的降级处理逻辑。使用HystrixCommond来完成
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallBack")
public User queyById(@PathVariable("id") Long id) {
//根据服务id获取服务实例列表
String url = "http://user-service/user/" + id;
System.out.println("url=========" + url);
User user = restTemplate.getForObject(url, User.class);
return user;
}
public String queryByIdFallBack(Long id) {
log.error("查询用户信息失败id:{}", id);
return "网络错误";
}
- @HystrixCommand(fallbackMethod="queryByIdFallBack"):用来声明一个降级逻辑的方法
需要注意的是,熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。
测试:
当user-service正常提供服务时,访问服务与之前一致
我们将user-service停掉后,发现页面返回了降级处理信息
默认的Fallback
我们刚才是指定了Fallback,那如果需要服务降级的方法很多,我们岂不是要写很多的fallback,我们可以把Fallback配置加载类上,实现默认fallback
@DefaultProperties(defaultFallback="defaultFallBack")
@RestController
@RequestMapping("consumer")
@Slf4j
@DefaultProperties(defaultFallback = "queryByIdFallBack")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
@HystrixCommand
public String queyById(@PathVariable("id") Long id) {
//根据服务id获取服务实例列表
String url = "http://user-service/user/" + id;
System.out.println("url=========" + url);
String user = restTemplate.getForObject(url, String.class);
return user;
}
public String queryByIdFallBack(Long id) {
log.error("查询用户信息失败id:{}", id);
return "网络错误";
}
}
超时设置
在之前的案例中,请求在超过1秒后会返回错误信息,这是因为Hystrix的默认超时时长为1秒,我们可以在配置中修改这个值
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
这个配置会作用于全局所有方法
服务熔断
熔断原理
熔断器,也叫断路器。Circuit Breaker
Hystrix的熔断状态机模型
状态机有3个状态
- Closed:关闭状态,所有请求都正常访问
- Open:打开状态,所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阀值,则触发熔断,断路器会完全关闭。默认失败比例的阀值是50%,请求次数最少不低于20次
- Half Open 半开状态。open状态不是永久的,打开后会进入休眠时间,随后断路器会自动进入半开状态,此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠
实战
为了能够精确控制请求的成功或失败,我们在consumer的调用业务中加入一些逻辑
@GetMapping("{id}")
@HystrixCommand
public String queyById(@PathVariable("id") Long id) {
if (id == 1) {
throw new RuntimeException("busy");
}
//根据服务id获取服务实例列表
String url = "http://user-service/user/" + id;
System.out.println("url=========" + url);
String user = restTemplate.getForObject(url, String.class);
return user;
}
在这个逻辑中,如果参数id为1,则一定会失败,其它情况都成功
我们准备两个请求窗口
- http://localhost:8080/consumer/1 注定失败
- http://localhost:8080/consumer/2 注定成功
修改熔断器默认参数:
circuitBreaker:
requestVolumeThreshold: 10
sleepWindowInMilliseconds: 100
errorThresholdPercentage: 50
- requestVolumeThreshold:触发熔断的最小请求次数,默认20
- sleepWindowInMilliseconds:休眠时长,默认5000毫秒
- errorThresholdPercentage:触发熔断的失败请求最小占比 默认50%
我们的操作是:疯狂的对id=1进行请求,就会触发熔断,断路器会断开,一切请求都会被降级处理,此时访问id=2,发现返回的也是失败,而且失败时间只有20毫秒左右