配套资料,免费下载
链接:https://pan.baidu.com/s/1la_3-HW-UvliDRJzfBcP_w
提取码:lxfx
复制这段内容后打开百度网盘手机App,操作更方便哦
Hystrix是由Netflix开源的一个服务隔离组件,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。
在分布式系统,我们一定会依赖各种服务,那么这些个服务一定会出现失败的情况,Hystrix就是这样的一个工具,它通过提供了逻辑上延时和错误容忍的解决力来协助我们完成分布式系统的交互。Hystrix通过分离服务的调用点,阻止错误在各个系统的传播,并且提供了错误回调机制,这一系列的措施提高了系统的整体服务弹性。
当一个目标服务执行时间过长,为了不让客户端无意义盲目等待,此时会立刻返回一个友好提示,比如:服务器过忙,请稍后再试,这就是服务降级。程序运行异常、程序运行超时、服务熔断都会触发服务降级、线程池/信号量打满也会触发服务降级。
这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源,如果目标服务情况好转则恢复调用。
上述两种模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
我们接下来的所有操作均是在OpenFegin
最后完成的工程上进行操作,相关代码请到配套资料中寻找。
(1)我们接下来的所有操作均在service-consumer9002
工程中进行,请在pom.xml中添加Hystrix的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
(2)修改application.yaml
,将Ribbon的超时时间全部注释,方便接下来测试
#ribbon:
# ReadTimeout: 5000
# ConnectTimeout: 5000
(3)修改service-provider8001
的ProductController
的findByPid
方法,根据pid分别模拟了运行正常、运行错误、超时等情况
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) throws InterruptedException {
//模拟:pid如果小于0说明出错
if (pid < 0) {
throw new RuntimeException("***** pid 不能负数 *****"); }
//模拟:pid如果等于0说明业务正常
if (pid == 0) {
System.out.println("***** pid 业务正常 *****"); }
//模拟:pid如果大于0说明业务超时
if (pid > 0) {
Thread.sleep(3000); }
//结果:返回服务端口+方法+线程名
return "8001 findByPid " + Thread.currentThread().getName();
}
(4)修改service-provider8002
的ProductController
的findByPid
方法,根据pid分别模拟了运行正常、运行错误、超时等情况
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) throws InterruptedException {
//模拟:pid如果小于0说明出错
if (pid < 0) {
throw new RuntimeException("***** pid 不能负数 *****"); }
//模拟:pid如果等于0说明业务正常
if (pid == 0) {
System.out.println("***** pid 业务正常 *****"); }
//模拟:pid如果大于0说明业务超时
if (pid > 0) {
Thread.sleep(3000); }
//结果:返回服务端口+方法+线程名
return "8002 findByPid " + Thread.currentThread().getName();
}
(5)依次启动如下程序
(6)依次输入网址来查看
调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1
调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0
调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1
正因为有上述故障或不佳表现,才有我们的降级/熔断/限流等技术诞生。
注意:服务降级既可以放到
服务提供端
,也可以放到服务消费端
,因为服务消费端的controller
是调用的服务提供端的controller
,都是controller
,那就都可以用,没有什么区别,不过需要注意一点就是第(2)步的配置,服务提供端
是不需要配置这一段内容的,服务消费端
就必须要提供这一段配置了。
(1)在入口类中使用@EnableCircuitBreaker
注解或@EnableHystrix
开启断路器功能,也可以使用一个名为@SpringCloudApplication
的注解代替主类上的三个注解(@SpringBootApplication
、@EnableDiscoveryClient
(默认自动开启)、@EnableCircuitBreaker
),推荐@EnableHystrix
,修改后如下:
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class ServiceConsumer9002Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9002Application.class);
}
}
(2)在application.yaml
中新增一段代码:
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。如果服务降级放到服务提供端不需要配置这一段代码
(3)在对应的控制器类ProductController
上你所要对哪个方法进行服务降级,就对哪个方法进行特殊处理,这里我对findByPid
进行处理,处理代码如下:
@HystrixCommand(fallbackMethod = "findByPidFallback", commandProperties = {
/*这里用来放常见的配置,比如:该方法的超时时间是多少*/
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
@RequestMapping("/consumer/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
return productFeignService.findByPid(pid);
}
/**
* 服务降级方法,相当于findByPid方法的默认返回值
* 由上边的从数据库中查出来的动态数据 -》 服务出错调用这个降级方法返回默认值的过程就是服务降级
*
* @param pid
* @return 返回值类型要和原方法保持一致
*/
public String findByPidFallback(@RequestParam("pid") Integer pid) {
return "发生故障,服务降级 ...";
}
(4)重启service-consumer9002
,因为有时候热部署并不能很好的帮我们自动重启服务,手动重启保险
(5)依次输入网址来查看
调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1
调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0
调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1
注意:hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发服务降级方法fallback,而修改这个默认时间通常有两种解决办法:
第一种:注解属性修改
@RequestMapping("/XXX/XXX") @HystrixCommand(fallbackMethod = "findByPidFallback", commandProperties = { /*这里用来放常见的配置,比如:该方法的超时时间是多少*/ @HystrixProperty(name = "execution.timeout.enabled", value = "true"),//默认为true,可以不配置 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") })
第二种:配置文件配置
ribbon.ReadTimeout=6000 ribbon.ConnectTimeout=3000 hystrix.command.default.execution.timeout.enabled=true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
这里有个坑需要注意一下:
如果
hystrix.command.default.execution.timeout.enabled
为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是断路器hystrix的timeoutInMilliseconds,此时谁的值小谁生效;如果
hystrix.command.default.execution.timeout.enabled
为false,则断路器不进行hystrix的超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,而ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;ribbon.ReadTimeout=6000 ribbon.ConnectTimeout=3000
虽然我们可以使用上边那种方式,一个一个的给所有的方法添加服务降级的处理方法,但是,这样一来,你的控制层会多很多跟业务逻辑没有关系的代码,增加了很多代码,我们也成为代码膨胀,有没有一种方式,既可以处理每一个方法给他添加服务降级方法,又能跟控制器类中的方法进行解耦合,我们可以使用Fegin跟Hystrix相结合的方式来进行处理。
(1)我们打开ProductFeignService
,会发现这里边的代码不就是Feign
用于调用远程服务提供者所定义的接口方法,我们第一步就是重新编写一个类来继承这个接口,因为这个接口中的方法不就是我们ProductController
中调用的方法吗。
com.caochenlei.service.impl.ProductFeignServiceFallBackImpl
@Component
public class ProductFeignServiceFallBackImpl implements ProductFeignService {
@Override
public List<Product> findAll() {
return Arrays.asList(
new Product(1, "小米手机-服务降级 ...", 1000.0D, 100),
new Product(2, "华为手机-服务降级 ...", 2000.0D, 200),
new Product(3, "苹果手机-服务降级 ...", 3000.0D, 300)
);
}
@Override
public String findByPid(Integer pid) {
return "findByPid 服务降级 ...";
}
}
(2)你得告诉ProductFeignService
当调用远程方法失败后,你应该去哪一个类来找相对应的服务降级fallback来进行服务降级处理。
com.caochenlei.service.ProductFeignService
@Component
@FeignClient(value = "SERVICE-PROVIDER", fallback = ProductFeignServiceFallBackImpl.class)
public interface ProductFeignService {
...
}
(3)重新启动service-consumer9002
,启动完成以后,手动强制关闭service-provider8001
、service-provider8002
来模拟服务提供者端宕机
(4)依次输入网址来查看
查看商品列表:http://localhost:9002/consumer/product/findAll
调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1
调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0
调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1
从上边的四个测试中不难看出,如果当前方法上已经增加了一个@HystrixCommand
,则会有优先执行自己定义的,如果自己没有定义服务降级的方法,则会走ProductFeignServiceFallBackImpl
中定义的方法。这样一来就实现了业务逻辑和fallback解耦操作了。
我们不确定我们自己的ProductController
中所写的方法一定是服务提供者
中的方法,我们也有可能有自己的方法,那样的话,使用上边的处理方式,好像似乎会有点问题,我们总不能把我们自己特有的方法也写到ProductFeignService
这个接口中吧,你不写到这个接口中,ProductFeignServiceFallBackImpl
就没办法实现对应方法的fallback服务降级方法,你当然可以使用第一种的一个一个的使用注解@HystrixCommand
来配,但是万一有100多个呢,那你不傻眼了吗,有没有一种可以统一处理全局的服务降级方法,这个全局就是,如果,你自己有指定的我就用你指定的(多个方法降级
的也算指定的),你没有指定,都交给我来处理,具体的做法请参考如下步骤:
(1)首先我们需要在service-consumer9002
的ProductController
中添加2个独有的控制器方法,并标注@HystrixCommand
注解,代码如下:
@HystrixCommand
@RequestMapping("/consumer/product/findOne")
public String findOne() {
int a = 1 / 0;//模拟异常
return "findOne 正常方法 ...";
}
@HystrixCommand
@RequestMapping("/consumer/product/findTwo")
public String findTwo() {
int a = 1 / 0;//模拟异常
return "findTwo 正常方法 ...";
}
(2)在service-consumer9002
的ProductController
中编写一个全局服务降级的fallback方法,代码如下:
public String GlobalFallbackMethod(){
return "全局服务降级fallback ...";
}
(3)在service-consumer9002
的ProductController
上标注一个默认配置的注解,代码如下:
@RestController
@DefaultProperties(defaultFallback = "GlobalFallbackMethod") //全局的
public class ProductController {
...
}
(4)重新启动service-consumer9002
(5)依次输入网址来查看
查看商品列表:http://localhost:9002/consumer/product/findAll
调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1
调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0
调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1
findOne:http://localhost:9002/consumer/product/findOne
findTwo:http://localhost:9002/consumer/product/findTwo
注意:这个全局服务降级的fallback方法也不是随便写的,至少你的返回值类型都应该和原方法保持一致,有人可能会问,我有的查询一件商品,有的查询商品列表,这怎么可能返回一致,那这种方法是不是没有效果了,我们可以写一个返回结果包装类,来统一处理返回结果,我这里给大家提供一种常见的包装类形式,仅供参考:
/** * 统一处理返回结果 * * @author CaoChenLei */ @Data @NoArgsConstructor @AllArgsConstructor public class Result implements Serializable { private Integer code;//状态码,比如:200 private String msg;//消息标题,比如:查询商品列表成功 private Object data;//这个data里边就是具体返回的数据,什么类型都支持,如果没有数据就返回null }
(1)重新启动service-provider8001
,service-provider8002
,服务提供者
(2)首先访问:http://localhost:9002/consumer/product/findByPid?pid=0,我们发现可以正常访问
(3)再次访问:http://localhost:9002/consumer/product/findByPid?pid=-1,快速访问10次错误的,然后在访问一次正确的,你会发现正确的不能访问了(能访问再多刷新几次,反复尝试),然后你就不停的访问正确的,发现一会正确的又可以访问了,这个就是服务的熔断
hystrix限流就是限制你某个微服务的使用量(可用线程数、信号量),hystrix通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小10个) 管理你的所有微服务,你可以给某个微服务开辟新的线程池:
(1)在service-consumer9002
的ProductController
添加如下代码:
/**
* threadPoolKey:是线程池唯一标识,hystrix会使用该标识来计数,看线程占用是否超过了,超过了就会直接降级该次调用
* 这里coreSize给他值为2,那么假设你这个方法调用时间是1s执行完,那么在1s内如果有超过2个请求进来的话,剩下的请求则全部降级
* 其中maxQueueSize是一个线程队列,里面只能放1个请求线程,本来线程数有2个,队列里面允许放一个,那么总共只能有3个请求线程执行,如果超过了就会限流
*/
@HystrixCommand(fallbackMethod = "miaoShaFallback",
threadPoolKey = "miaoSha",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "2"),
@HystrixProperty(name = "maxQueueSize", value = "1")
})
@RequestMapping("/consumer/product/miaoSha")
public String miaoSha() throws InterruptedException {
Thread.sleep(500);//模拟业务逻辑消耗的时间
return "恭喜你,抢到了,^_^";
}
public String miaoShaFallback() {
return "秒杀高峰期,线程池已满,服务降级...";
}
(2)如果浏览器快速访问4次,可能并不会看到效果,我这里采用Apache JMeter来进行并发测试,设置了4个线程同时进行访问,结果到了第4个就服务限流了
Hystrix 仪表盘(Hystrix Dashboard),就像汽车的仪表盘实时显示汽车的各项数据一样,Hystrix 仪表盘主要用来监控 Hystrix 的实时运行状态,通过它我们可以看到 Hystrix 的各项指标信息,从而快速发现系统中存在的问题进而解决它,要使用 Hystrix 仪表盘功能,我们首先需要有一个Hystrix Dashboard项目,这个功能我们可以在原来的消费者应用上添加,让原来的消费者应用具备 Hystrix 仪表盘功能,但一般地,微服务架构思想是推崇服务的拆分,Hystrix Dashboard 也是一个服务,所以通常会单独创建一个新的工程专门用做Hystrix Dashboard服务。
(1)创建一个新的子工程,名字叫hystrix-dashboard6001
(2)导入工程所需要的依赖
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
dependencies>
(3)创建一个启动类,并打开Hystrix的面板
com.caochenlei.HystrixDashboard6001Application
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard6001Application {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard6001Application.class);
}
}
(4)编写配置文件application.yaml
server:
port: 6001
hystrix:
dashboard:
proxy-stream-allow-list: "*"
(5)我们找到你要监控的服务,这里我们需要配置service-consumer9002
的启动类ServiceConsumer9002Application
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class ServiceConsumer9002Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9002Application.class);
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
(6)重新启动service-consumer9002
和hystrix-dashboard6001
(7)我们先访问以一下业务看看是不是正常(这一步必须做):http://localhost:9002/consumer/product/findByPid?pid=0
(8)把需要监控的地址http://localhost:9002/hystrix.stream
填入http://localhost:6001/hystrix