hystrix中的熔断是什么
-
家里保险丝烧断,家里就会停电
-
比如抢购接口,发生高并发调用时,在一段时间内,会有成功,会有失败,当请求次数达到N次,并且失败率达到一定比例后,hystrix熔断器会开启,就会发生熔断,此时再调用这个抢购接口,会直接走降级接口
-
circuitBreaker.enabled 是否开启断路器功能 circuitBreaker.requestVolumeThreshold 该属性设置在滚动时间窗口中,断路器的最小请求数。默认20,如果在窗口时间内请求次数19,即使19个全部失败,断路器也不会打开 circuitBreaker.sleepWindowInMilliseconds 改属性用来设置当断路器打开之后的休眠时间,休眠时间结束后断路器为半开状态,断路器能接受请求,如果请求失败又重新回到打开状态,如果请求成功又回到关闭状态 circuitBreaker.errorThresholdPercentage 该属性设置断路器打开的错误百分比。在滚动时间内,在请求数量超过circuitBreaker.requestVolumeThreshold,如果错误请求数的百分比超过这个比例,断路器就为打开状态 circuitBreaker.forceOpen true表示强制打开断路器,拒绝所有请求 circuitBreaker.forceClosed true表示强制进入关闭状态,接收所有请求
- circuitBreaker.requestVolumeThreshold:确定是否熔断监控时间内需要达到的请求阈值,默认20
- circuitBreaker.errorThresholdPercentage:请求失败率,默认50%
-
metrics.rollingStats.timeInMilliseconds 设置滚动时间窗的长度,单位毫秒。这个时间窗口就是断路器收集信息的持续时间。断路器在收集指标信息的时会根据这个时间窗口把这个窗口拆分成多个桶,每个桶代表一段时间的指标,默认10000 metrics.rollingStats.numBuckets 滚动时间窗统计指标信息划分的桶的数量,但是滚动时间必须能够整除这个桶的个数,要不然抛异常
- metrics.rollingStats.timeInMilliseconds:是否发生熔断的监控时间,默认10秒
-
上述三个指标都达到,才会发生熔断
-
隔一段时候后,熔断器会由开启状态变成半开状态
-
如果熔断器一直开启,这个接口永远调不通
-
半开状态是允许接收一次用户请求,如果请求成功,熔断器就会由半开状态变成失败状态,如果请求失败,就会变成开启状态
-
并且这个隔一段时间,是可以配置的
-
circuitBreaker.sleepWindowInMilliseconds 改属性用来设置当断路器打开之后的休眠时间,休眠时间结束后断路器为半开状态,断路器能接受请求,如果请求失败又重新回到打开状态,如果请求成功又回到关闭状态
-
-
-
总结
-
hystrix默认在10秒之内,如果请求数量超过20个,并且请求失败率在50%以上时就会开启熔断器,此时如果仍然调用服务提供方,是调用不到的
-
但是熔断器并不会一直开启,过一段时间的休眠期之后(默认是5秒),熔断器会有开启状态变成半开启状态,此时是允许接收用户请求的,如果请求成功,熔断器则会变成关闭状态,但是如果失败,则会变成开启状态
-
熔断器状态
- 开启:用户请求不能请求后端的服务提供方,直接走降级
- 关闭:请求可以达到服务提供方
- 半开:能够接受用户请求,如果成功,会变成关闭,如果失败,则会变成开启
测试实例
-
@RequestMapping("/errorMessage") public String errorMessage(Integer id) { try { return studentService.errorMessage(id); }catch (FeignException e) { e.printStackTrace(); return e.getMessage(); } }
压测请求
- 用3000并发去压(上一节中的jmeter测试例子),请求url为/user/queryUser,过了几秒钟之后,会发现熔断器功能会开启,此时去请求接口,直接是失败的,但是再过了几秒钟之后,发现又能够请求成功了
feign的使用
- 如果是流程比较复杂的调用,不太建立使用feign,而是使用@HystrixCommand的配置方法更好,因为前者是对hystrix的封装,性能有一定损耗
feign解决了什么问题
- 1.把共用代码抽提出来
- 2.针对调用方,业务逻辑比较简单的代理
实例-业务逻辑比较简单的代理
服务调用方
-
package com.xiangxue.jack.controller; import com.xiangxue.jack.bean.Student; import com.xiangxue.jack.service.feign.StudentService; import feign.FeignException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; @RequestMapping("/getAllStudent") public String getAllStudent() { return studentService.getAllStudent(); } @RequestMapping("/getStudentById") public String getStudentById(Integer id) { return studentService.getStudentById(id); } @RequestMapping("/errorMessage") public String errorMessage(Integer id) { try { return studentService.errorMessage(id); }catch (FeignException e) { e.printStackTrace(); return e.getMessage(); } } @RequestMapping("/saveStudent") public String saveStudent(@RequestBody Student student) { return studentService.saveStudent(student); } @RequestMapping("/timeOut") public String timeOutTest(@RequestParam int millis) { long t1 = System.currentTimeMillis(); String cacheResult = studentService.queryStudentTimeout(millis); long t2 = System.currentTimeMillis(); log.info("======调用provider耗时:" + (t2 - t1) + "ms"); return cacheResult; } }
-
package com.xiangxue.jack.service.feign; import com.xiangxue.jack.bean.Student; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; /* * fallback = StudentServiceFallback.class * 不能获取具体异常 * * */ @FeignClient(name = "MICRO-ORDER",path = "/feign" /*fallback = StudentServiceFallback.class,*/ ,fallbackFactory = StudentServiceFallbackFactory.class) public interface StudentService { @GetMapping("/student/getAllStudent") String getAllStudent(); @PostMapping("/student/saveStudent") String saveStudent(@RequestBody Student student); @GetMapping("/student/getStudentById") String getStudentById(@RequestParam("id") Integer id); @GetMapping("/student/errorMessage") String errorMessage(@RequestParam("id") Integer id); @GetMapping("/student/queryStudentTimeout") String queryStudentTimeout(@RequestParam("millis") int millis); }
服务提供方
-
package com.xiangxue.jack.controller; import com.xiangxue.jack.bean.Student; import com.xiangxue.jack.service.feign.StudentService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @Slf4j @RestController public class StudentController implements StudentService { @Autowired private StudentService studentService; @RequestMapping("/feign/student/getAllStudent") @Override public String getAllStudent() { return studentService.getAllStudent(); } @RequestMapping("/feign/student/getStudentById") @Override public String queryStudentById(@RequestParam("id") Integer id) { return studentService.queryStudentById(id); } @RequestMapping("/feign/student/saveStudent") @Override public String saveStudent(@RequestBody Student student) { return studentService.saveStudent(student); } @RequestMapping("/feign/student/errorMessage") @Override public String errorMessage(@RequestParam("id") Integer id) { return studentService.errorMessage(id); } @RequestMapping("/feign/student/queryStudentTimeout") @Override public String queryStudentTimeout(@RequestParam("millis") int millis) { log.info("provider--->" + millis); try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } return "provider--->" + millis; } }
-
package com.xiangxue.jack.service.feign; import com.xiangxue.jack.bean.Student; public interface StudentService { String getAllStudent(); String saveStudent(Student student); String queryStudentById(Integer id); String errorMessage(Integer id); String queryStudentTimeout(int millis); }
-
package com.xiangxue.jack.service.feign; import com.alibaba.fastjson.JSONObject; import com.xiangxue.jack.bean.Student; import com.xiangxue.jack.dao.StudentMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Override public String getAllStudent() { String result = JSONObject.toJSONString(studentMapper.getAllStudent()); return result; } @Override public String saveStudent(Student student) { int i = studentMapper.saveStudent(student); if(i == 1) { return "S"; } return "F"; } @Override public String queryStudentById(Integer id) { Student student = studentMapper.queryStudentById(id); return JSONObject.toJSONString(student); } @Override public String errorMessage(Integer id) { try { Student student = studentMapper.queryStudentById(id); int a = 1 / 0; return JSONObject.toJSONString(student); }catch (Exception e) { e.printStackTrace(); // return e.getMessage(); throw e; } } @Override public String queryStudentTimeout(int millis) { return null; } }
总结
- 服务提供方法和服务调用方中定义的接口名称可以不同,但是服务提供方的请求url是/feign加上服务调用方的请求url
- 服务调用方只有服务层的接口,没有实现类
- 关于调用方法时的入参对象怎么传过去?
- feign传对象,加了@RequestBody注解,转成json字符串,两边都不需要序列化
- feign传普通类型或其包装类,加@RequestParam(“参数名”)的注解,不然会报错
feign总结
- feign是对hystrix和ribbon的再封装,默认所有的接口都是服务隔离的,比如在上面服务调用方的每一个接口方法上,可以理解为自动加上了@HystrixCommand的注解,如果这个端口的服务被springcloud监控了,就可以在监控面板上看到响应的接口调用情况,但是实际没有加@HystrixCommand的注解
实例-公用代码抽提
服务调用方
-
package com.xiangxue.jack.service.feign; import com.xiangxue.jack.api.TeacherService; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(name = "MICRO-ORDER") public interface TeacherServiceFeign extends TeacherService { }
服务提供方
-
package com.xiangxue.jack.controller; import com.xiangxue.jack.api.TeacherService; import com.xiangxue.jack.bean.Teacher; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TeacherController implements TeacherService { @Override public String getAllTeacher() { return "micro-order.getAllTeacher"; } @Override public String saveTeacher(@RequestBody Teacher Teacher) { return "micro-order.saveTeacher"; } @Override public String getTeacherById(@RequestParam("id") Integer id) { return "micro-order.getTeacherById"; } @Override public String getTeacherByName(@PathVariable("name") String name) { return "micro-order.getTeacherByName"; } @Override public String errorMessage(@RequestParam("id") Integer id) { return "micro-order.errorMessage"; } }
总结
- TeacherService位于micro-service-api包中,是一个公用方法类,在一个公共项目中
- 好处是代码变精简了,缺点是系统的耦合性变高,一旦修改了公共代码,可能很多代码部分都需要调整
feign中的服务降级
-
@FeignClient(name = "MICRO-ORDER",path = "/feign" /*fallback = StudentServiceFallback.class,*/ ,fallbackFactory = StudentServiceFallbackFactory.class)
- 常用的是用fallbackFactory,一般不用fallback,因为fallback不能获取具体异常
StudentServiceFallbackFactory
-
package com.xiangxue.jack.service.feign; import com.xiangxue.jack.bean.Student; import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component public class StudentServiceFallbackFactory implements FallbackFactory<StudentService> { @Override public StudentService create(Throwable throwable) { if(throwable == null) { return null; } final String msg = throwable.getMessage(); log.info("exception:" + msg); return new StudentService() { @Override public String getAllStudent() { log.info("exception=====getAllStudent==========" + msg); return msg; } @Override public String saveStudent(Student student) { log.info("exception=====saveStudent==========" + msg); return msg; } @Override public String getStudentById(Integer id) { log.info("exception=====getStudentById==========" + msg); return msg; } @Override public String errorMessage(Integer id) { log.info("exception=====errorMessage==========" + msg); return msg; } @Override public String queryStudentTimeout(int millis) { log.info("exception=====queryStudentTimeout==========" + msg); return msg; } }; } }
- public StudentService create(Throwable throwable)中的throwable就是具体的异常
fegin的全局过滤器
-
package com.xiangxue.jack.service.feign; import feign.Response; import feign.Util; import feign.codec.ErrorDecoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; @Configuration public class FeignErrMessageFilter { @Bean public ErrorDecoder errorDecoder() { return new FeignErrorDecoder(); } /* * 当调用服务时,如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中 * {"timestamp":"2020-02-17T14:01:18.080+0000","status":500,"error":"Internal Server Error","message":"/ by zero","path":"/feign/student/errorMessage"} * 只有这种方式才能获取所有的被feign包装过的异常信息 * * 这里如果创建的Exception是HystrixBadRequestException * 则不会走熔断逻辑,不记入熔断统计 * */ class FeignErrorDecoder implements ErrorDecoder { private Logger logger = LoggerFactory.getLogger(FeignErrorDecoder.class); @Override public Exception decode(String s, Response response) { RuntimeException runtimeException = null; try { String retMsg = Util.toString(response.body().asReader()); logger.info(retMsg); // 可以把异常信息包装成自定义异常类 // 这里是简单用运行时异常包装 runtimeException = new RuntimeException(retMsg); } catch (IOException e) { e.printStackTrace(); } return runtimeException; } } }
-
实际调用时先经过过滤器,之后如果有服务降级,再走服务降级
-
当调用服务时,如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中
-
这个过滤器中可以把异常包装成自定义的异常类,这样就可以有自己的一套异常码,前端就根据这个异常做相应的处理
dubbo与hystrix对比
- dubbo中没有熔断功能,但是要做到熔断开启的效果,只有修改dubbo-consumer.xml为:<dubbo:reference inteface=“xx.xx” id=“xx” … mock=“return null”/>
- 这里修改的配置是服务提供方,当启动服务调用方,但是不启动服务提供方,调用时直接会返回null
- 新建一个UserServiceMock,其中对应接口的实现为服务降级,此时修改上述的标黄部分,修改为mock"true",此时再调用,则会走到这个UserServiceMock中的降级方法
dubbo中的标签解析
- 为了找<dubbo:reference />
- 1.先找到dubbo的jar包,maven:com.alibaba:dubbo:2.5.3
- 2.找到其中的spring.handlers,拉到最后,点进里面的DubboNamespaceHandler
- 3.从其中找到reference的标签解析
- 4.再点进ReferenceBean,它实现了FactoryBean,而FactoryBean从spring源码可知它可以生产代理,因为需要实现getObject()方法,其中又有一个get()方法,其中又有一个init()方法,首先解析标签,生成URL对象,dubbo://ip:port?timeout=xx,然后是一系列的参数封装,最后创建代理createProxy()
- 5.然后又进入proxyFactory.getProxy()方法,在java中用的是JavassistProxyFactory的实现
- 6.其中又有一个InvokerInvocationHandler,其中实现了invoke方法
- 最先invoke的是mock,对应的是MockClusterInvoker,如果是value等于true,继续进行invoker调用链的调用,如果发生了异常,则会执行doMockInvoke方法,进入其中,继续调用minvoker.invoke,这里是进入MockInvoker这个类中,getInvoker中会取出一个类,而这个就是自己调用的方法名UserService+"Mock"组装而成,这是代码写死的规则,而这个类的实例就是通过字节码生成拿到的
- 上述标黄的详细的invoker调用链的调用,对于MockClusterInvoker,首先会来到它的抽象父类AbstractClusterInvoker,其中有一个list(invocation)方法,但是这里面是空的,因为RegistryDirctory本地是没有对象的,因为在上述的测试中生成者没有启动,所以就会抛出异常进入上一个段落的异常处理,如果启动生成者,通过zk的watcher机制把注册信息同步过来,此时invoker就会有值了,通过负载均衡获取一个,而负载均衡的对象是通过dubbo中的spi机制获得,然后继续调用doInvoke方法,这是父类里面的,会调用子类的doInvoke方法,为FailoverClusterInvoker,其中len是重试次数,一个for循环,同时检查一下invoker有没有下线,select就是根据负载均衡选一个取调,然后就会来到invoker.invoke,这里会进入RegistryDirectory$invokerDelegete,跳入这个类,在InvokerDelegete的父类InvokerWrapper中,有一个invoke方法,这里又有一个invoker,这个invoker是dubbo过滤器定义的地方,实际上过滤器返回的是一个匿名对象,ProtocolFilterWrapper$1,$1肯定是一个内部类,在这个类中,有一个refer方法,这是在标签解析时会调用,然后又会调用buildInvokerChain方法,返回的是Invoker的匿名对象,在buildInvokerChain方法中,List filters也是通过spi机制获取的,里面有很多个,跟LoadBalance一样的配置方法,在这个匿名类中,invoke方法里面最后调用的才是真正的rpc方法,
- dubbo中的spi机制是改进过的,有key和value,找到LoadBalance的文件,在dubbo.internal文件目录下,默认值是随机类型的
- 在List filters中,有一个MonitorFilter,是用来监控数据的,如果配置了就会使用,不然根据责任链模式就会调下一个,最终会调用ListenerInvokerWrapper中的invoke方法,这个又会调到DubboInvoker,这就是真正的rpc调用,所以dubbo没有熔断,只有降级,必然会调用
- 在DubboInvoker中又分为单工和双工,而这又涉及到心跳,而明显是通过future的方式调用的,执行currentClinet.request(inv, timeout).get(),这里会用到exchange模型,exchange类似于线程池,每一个连接过来,exchange分配一个线程去调用
总结
- dubbo不管是什么mock,return null还是什么,都会走invoker.invoke调用,而这个mock是在invoker.invoke发生了异常才会去做的
- 而springcloud熔断是open状态则不会去调用后端服务了