【SpringCloud】(三)Hystrix 与 Zuul
5 Hystrix
Hystrix:一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖会不可避免得调用失败,比如超时、异常等,Hystrix能保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
扇出
:后面调用失败导致前面一串失败的情况。
服务雪崩
:
5.1 服务熔断机制
熔断机制是应对服务雪崩效应
的一种微服务链路保护机制
。
5.2 Hystrix熔断代码实现
代码实现则是出现异常的时候去执行另一部分方法调用,但是远程方法调用是要求返回类型相同的,所以这里使用ResultEntity既能返回成功也能返回失败的信息。
① 在provider引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
② 在provider主启动类启用断路器功能
@EnableCircuitBreaker @SpringBootApplication public class HikaruMainType { public static void main(String[] args) { SpringApplication.run(HikaruMainType.class); } }
Circuit为环路的意思
这里也可以使用最新的注解 @EnableHystrix
③ 封装一个ResultEntity作为Ajax请求或者远程方法调用的数据格式
public class ResultEntity<T> { public static final String SUCCESS = "SUCCESS"; public static final String FAILURE = "FAILURE"; public static final String NO_MESSAGE = "NO_MESSAGE"; public static final String NO_DATA = "NO_DATA"; private String result; private T data; private String message; public ResultEntity() { } public ResultEntity(String result, T data, String message) { this.result = result; this.data = data; this.message = message; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public static ResultEntity<String> SuccessWithoutData() { return new ResultEntity<String>(SUCCESS, NO_DATA, NO_MESSAGE); } public static <E> ResultEntity<E> SuccessWithData(E data) { return new ResultEntity<E>(SUCCESS, data, NO_MESSAGE); } /** * 失败返回错误消息 * @param Message * @param <E> * @return */ public static <E> ResultEntity<E> Failure(String Message) { return new ResultEntity<E>(FAILURE, null, Message); } }
④ 编写Provider的handler方法:指定服务熔断降级方案
@HystrixCommand(fallbackMethod = "getEmpBackUp") @RequestMapping("/provider/circuit/breaker/get/employee") public ResultEntity<Employee> getEmp(@RequestParam("signal") String signal) throws InterruptedException { if("quick-bang".equals(signal)) { throw new RuntimeException(); } else if("slow-bang".equals(signal)) { Thread.sleep(1000); } return ResultEntity.SuccessWithData(new Employee(6, "66", 666)); } public ResultEntity<Employee> getEmpBackUp(@RequestParam("signal") String signal) { return ResultEntity.Failure("Circuit works with signal" + signal); }
⑤ 超时熔断不生效的情况:Feign里面Ribbon的超时时间小于了Hystrix超时熔断时间
使用上面的配置,在Consumer端口访问http://localhost:7000/consumer/get/emp/with/ciruit/breaker?signal=slow-bang会发现直接抛出异常而不是我们期待的封装的失败返回体,这里的原因是Feign里面Ribbon的超时时间小于Hystrix超时熔断时间 ,简单来说就是Ribbon带着服务名去Eureka注册中心调用远程方法迟迟没有响应而达到了它的最大值,于是在Hystrix熔断的超时时间之前就返回了异常信息。
修改也很简单,只需要改一下Feign里面的Ribbon的ConnectTimeOut和ReadTimeOut即可:
feign: client: config: default: ConnectTimeOut: 5000 ReadTimeOut: 5000
Ribbon的 ConnectTimeOut 和 ReadTimeOut
ConnectTimeOut
是ribbon
和eureka
两端建立连接所用的时间,在java中,网络状况正常的情况下,例如使用HttpClient或者HttpURLConnetion连接时设置参数connectTimeout=5000即5秒,如果连接用时超过5秒就是抛出java.net.SocketException: connetct time out的异常。
ReadTimeOut
则是读取数据花费的时间,如果在指定时间读不到数据,则会抛出Java.net.SocketException: read time out的异常,这也正是我们这里控制台日志中的异常。
一般来说前者设置得很小而后者设置得很大,原因是一般很少有连接超时的情况,但是经常有由于网络波动下载读取的情况。
5.3 Hystrix降级
服务降级处理是在客户端(Consumer) 实现的,与服务端(Provider)没有关系。当某个Consumer访问一个Provider迟迟得不到响应的时候,会去执行一个预先设定好的方案,而不是一直等待。
降级是consumer得不到回应采取的补救措施
熔断是provider响应不了实例采取的补救措施
① 在common模块创建FallBack工厂类
实现Consumer服务降级功能时要传入 @FeinClient 注解标记的类型,然后在create()返回一个 @FeignClient 注解标记的接口类型的对象,当Provider调用失败之后就会调用这个对象中的方法
/** * 实现Consumer服务降级功能时要传入 @FeinClient 注解标记的类型 * 然后在create()返回一个 @FeignClient 注解标记的接口类型的对象,当Provider调用失败之后就会调用这个对象中的方法 */ @Component public class MyFallBackFactory implements FallbackFactory<EmployeeRemoteService> { @Override public EmployeeRemoteService create(Throwable throwable) { return new EmployeeRemoteService() { @Override public Employee getEmployeeRemote() { return null; } @Override public ResultEntity<Employee> getEmp(String signal) { return ResultEntity.Failure(throwable.getMessage() + "降级机制生效"); } }; } }
别忘了在工厂类上面添加@Component注解,否则无法加入IOC容器
② 在common模块的远程方法调用接口上指明降级使用的工厂类
@FeignClient(value = "hikaru-provider", fallbackFactory = MyFallBackFactory.class) public interface EmployeeRemoteService { /** * 远程调用的接口方法 * 要求: * ① @RequestMapping地址一致 * ② 方法声明一致 * ③ 用来获取请求参数的@RequestParam、@PathVariable、@RequestBody一致 * @return */ @RequestMapping("/provider/get/employee/remote") public Employee getEmployeeRemote(); @RequestMapping("/provider/circuit/breaker/get/employee") public ResultEntity<Employee> getEmp(@RequestParam("signal") String signal); }
可以看到这里是在注解上指定的工厂类,所以也解释了上面为什么要将工厂类添加到IOC容器
③ 在consumer端开启服务降级
eureka: client: service-url: defaultZone: http://localhost:5000/eureka server: port: 7000 tomcat: connection-timeout: 5000 feign: hystrix: enabled: true client: config: default: ConnectTimeOut: 5000 ReadTimeOut: 5000
⑤ 测试:http://localhost:7000/consumer/get/emp/with/ciruit/breaker?signal=123
{"result":"FAILURE","data":null,"message":"null降级机制生效"}
5.4 Hystrix仪表盘:监控服务的各种信息
在Provider引入依赖并配置application.yml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
management: endpoints: web: exposure: include: hystrix.stream
创建监控工程spring-cloud-dashboard
① 引入依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> </dependencies>
② 在主启动类开启仪表盘功能
@EnableHystrixDashboard @SpringBootApplication public class HikaruMainType { public static void main(String[] args) { SpringApplication.run(HikaruMainType.class); } }
③ 设置访问端口号和配置代理地址
server: port: 8000 spring: application: name: hikaru-dashboard hystrix: dashboard: proxy-stream-allow-list: "localhost"
访问监控工程
输入监控工程地址\http://localhost:8000/hystrix,然后输入 http://localhost:3000/actuator/hystrix.stream 以及Delay(默认2000)监控延迟时间
监控延迟时间是为了防止给Provider造成压力
6 Zuul 网关
zuul为整个项目中的微服务模块提供一个统一的入口,请求经过网关再去访问微服务模块,不同微服务一般有不同地址,而外部的客户端可能会调用多个微服务的接口才能完成一个业务需求,如果让客户端和服务端直接进行通信:
-
客户端会多次请求不同的服务端,增加了客户端的复杂性
-
存在跨域问题
-
认证复杂,每一个微服务模块需要单独认证
-
难以重构:客户端和服务端之间的通信错综复杂
-
某些微服务可能使用了其他协议,直接访问可能有困难
zuul最主要的j几个功能:
路由
:将外部请求转发到具体的微服务实例上,是实现外部统一入口的基础
过滤
:负责对请求过程进行干预
代理
:Zuul自身会注册为eureka的应用,并获取eureka中其他微服务的信息,也即以后的微服务的访问都是通过Zuul代理跳转的。
6.1 代码实现
① 创建zuul工程并导入zuul依赖和eureka客户端依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
② 配置
server: port: 9000 spring: application: name: hikaru-zuul eureka: client: service-url: defaultZone: http://localhost:5000/eureka
③ 在主启动类开启zuul代理服务
@EnableZuulProxy @SpringBootApplication public class HikaruMainType { public static void main(String[] args) { SpringApplication.run(HikaruMainType.class); } }
6.2 zuul配置路由规则
自定义访问
在zuul工程中配置:
server: port: 9000 spring: application: name: hikaru-zuul eureka: client: service-url: defaultZone: http://localhost:5000/eureka zuul: routes: employee: service-id: hikaru-consumer path: /zuul-emp/**
重启之后访问http://localhost:9000/zuul-emp/consumer/get/emp/with/ciruit/breaker?signal=123可以访问,原来的地址通过微服务的名称也可以访问:localhost:9000/hikaru-consumer/consumer/get/emp/with/ciruit/breaker?signal=123
ignored-services: '*' 禁止微服务名称访问
server: port: 9000 spring: application: name: hikaru-zuul eureka: client: service-url: defaultZone: http://localhost:5000/eureka zuul: ignored-services: '*' routes: employee: service-id: hikaru-consumer path: /zuul-emp/**
prefix: 添加访问前缀
这里zuul的路由规则只适用于localhost:9000,zuul无法拦截其他微服务端口号的
6.3 zuul Filter
package com.hikaru.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.apache.http.protocol.RequestContent; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Slf4j @Component public class MyZuulFilter extends ZuulFilter { /** * 过滤器什么时候执行 * @return */ @Override public String filterType() { String filterType = "pre"; return filterType; } @Override public int filterOrder() { return 0; } /** * 判读是否过滤 * @return */ @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String signal = request.getParameter("signal"); return "hello".equals(signal); } /** * 过滤执行的操作 * @return * @throws ZuulException */ @Override public Object run() throws ZuulException { log.info("当前请求需要执行过滤,run方法执行了"); return null; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步