文章目录
四、Hystrix
1.简述
Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败,是一种保护机制。
2.雪崩问题
微服务中,服务之间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路。
如图所示,一个微服务在调用多个微服务的时候,这些服务又可能返回参数影响其他微服务的运行,但如果我们有一个微服务出现了问题,那么就会越来越阻塞,导致tomcat无法运行,从一个微服务导致更多的微服务出现异常,直至瘫痪。
而Hystrix解决雪崩问题的方法有两个:
1.线程隔断
2.服务熔断
3.线程隔离,服务降级
<1>简介
当我们使用了Hystrix之后会给每一个服务分配线程池,让每一个微服务占有的线程是有限的,也就不会占用到过量的服务器性能,令出错误的时候让服务器瘫痪。但是这个作用仅仅是不让服务器瘫痪,服务本身还是导致了阻塞,为了能让我们的程序正常的运行,所以我们就要让出错的服务进行降级,让程序正常运行,而具体方式就是设置一个相应时间,由于阻塞导致无法进行相应,这时,我们就进行服务降级。
<2>程序实现
要在服务的消费方进行降级处理,防止别人雪崩引起自己雪崩
首先先引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类开启熔断机制
@EnableCircuitBreaker//熔断
插句话,我们已经在启动类放了很多个注解了
其实我们是可以用一个新的注解将这三个注解进行取代的,就是@SpringCloudApplication
然后对我们的某个方法开启容错机制
@GetMapping("{id}")
@HystrixCommand(fallbackMethod="queryByIdFallBack")//降级
public String queryById(@PathVariable("id") Long id) {
//容错处理,如果此方法没有问题,就执行,如果有问题,就跳转到fallbackMethod所描述的方法内,但要求两个方法返回值一致
String url = "http://user-Service/user/id";
String user = restTemplate.getForObject(url, String.class);
return user;
}
public String queryByIdFallback(Long id) {
return "Sorry";
}
最后在服务的使用方进行一下模拟超时
package itcast.user.service;
import itcast.user.POJO.User;
import itcast.user.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return userMapper.selectByPrimaryKey(id);
}
}
然后重新运行
我们发现会触发。
以上我们展示的仅仅是单独给某一个方法实现降级,其实也是可以给一个大类统一进行降级或者线程隔离的。
直接在类上贴上一个标签:
然后在想要实现线程隔离或者降级的方法上直接标记上
@HystrixCommand
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id) {
//容错处理,如果此方法没有问题,就执行,如果有问题,就跳转到fallbackMethod所描述的方法内,但要求两个方法返回值一致
String url = "http://user-Service/user/id";
String user = restTemplate.getForObject(url, String.class);
return user;
不过我们如果在类上进行标记,就不能在补救的方法上填入参数了,因为是一个通用的fallBack
直接这样写即可
public String queryByIdFallback) {
return "Sorry";
}
然后重新运行
当然,如果我们想要设置一些属性也是可以的。内部封装了一些相关的方法,就像刚才我们手动设置的超时时间,也可以在这里手动更快捷的设置
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id) {
//容错处理,如果此方法没有问题,就执行,如果有问题,就跳转到fallbackMethod所描述的方法内,但要求两个方法返回值一致
String url = "http://user-Service/user/id";
String user = restTemplate.getForObject(url, String.class);
return user;
4.服务熔断
<1>熔断原理
- Close:关闭状态(断路器关闭),所有请求都正常访问。
- Open:打开状态(断路器打开,所有请求都会被降级)。Hystrix会对请求情况计数。,当一定时间内的失败请求百分比达到阈值,或者触发熔断,断路器会完全关闭。默认失败的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,Closed状态不是永久的,关闭后会进入休眠时间(默认是5s).随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全打开断路器,否则关闭,再次进行休眠计时。
<2>程序实现
以Key-Value的形式来实现熔断
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//设置最大重启情况,超过5次认定失败,10次就熔断
@HystrixProperty(name = "circuitBreaker.sleepWindowInMillisecond",value = "10000"),//设置睡觉10秒不响应就会熔断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//错误超过60%就会失败
})
@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id) {
if (id % 2 == 0) {
throw new RuntimeException("自动报错");
}
//容错处理,如果此方法没有问题,就执行,如果有问题,就跳转到fallbackMethod所描述的方法内,但要求两个方法返回值一致
String url = "http://user-Service/user/id";
String user = restTemplate.getForObject(url, String.class);
return user;
//根据服务id来获取实例
// List<ServiceInstance> list = discoveryClient.getInstances("user-Service");
// ServiceInstance choose = ribbonLoadBalancerClient.choose("user-Service");
//从实例中取出ip地址和port
// ServiceInstance serviceInstance = list.get(0);
// String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
// 上面缩写的ribbon是属于轮询,一个一个去试,现在我们拦截到js,去判断
// String url = "http://user-Service/user/"+id;
// User user = restTemplate.getForObject(url, User.class);
// return user;
}
public String queryByIdFallback() {
return "Sorry";
}
然后重新运行程序
会发现频繁重启一个网站之后,会提示无法继续访问。
五、Feign
1.简介
在前面的学习中,我们使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:
String baseUrl = "http://user-service/user/";
User user = this.restTemplate.getForObject(baseUrl + id, User.class)
而Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
2.程序设计
<1>Feign的远程调用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后在consumer的启动类开启Feign远程调用的注解@EnableFeignClients
接下来创建Feign客户端:
package itcast.consumer.client;
import itcast.consumer.POJO.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("user-Service")
public interface UserClient {
@GetMapping("/user/{id}")
public User queryById(@PathVariable("id") Long id);
}
然后调用我们Feign客户端,完成简化
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id) {
return userClient.queryById(id);
}
来到启动类删除之前的底层,完成优化
// @LoadBalanced
// @Bean
// public RestTemplate restTemplate() {
// return new RestTemplate();
// }
另外,Feign内置了一个超时时常,我们也可以在配置文件中对其进行设置
ribbon:
ConnectionTimeOut: 500 #连接超时时常
ReadTimeOut: 2000 #读取超时时常
<2>Feign的熔断机制
首先先说一下,Hystrix默认Feign在中默认是关闭的,我们需要在配置文件中进行手动开启
feign:
hystrix:
enabled: true #开启Feign的熔断功能
然后写一个熔断接口
package itcast.consumer.client;
import itcast.consumer.POJO.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "user-Service",fallback = UserClientImpl.class)//标明熔断接口的实现类
public interface UserClient {//熔断接口
@GetMapping("/user/{id}")
public User queryById(@PathVariable("id") Long id);
}
实现熔断的业务逻辑,写出异常出现的时候所产生的现象。
package itcast.consumer.client;
import itcast.consumer.POJO.User;
import org.springframework.stereotype.Component;
@Component//注入到容器
public class UserClientImpl implements UserClient {//熔断的业务逻辑
@Override
public User queryById(Long id) {
User user = new User();
user.setName("未知错误!");
return user;
}
}
重新进行测试
由于我们设置的超时,所以进行了报错
测试成功