使用Hystrix实现服务容错

Hystrix

Hystrix是运行在客户端的,通过一定的手段,规避在不同但互相关联的服务调用中出现的故障和过分的延迟响应,从而防止发生服务雪崩。

Hystrix实现容错的手段

  • 服务降级
  • 服务熔断
  • 请求合并
  • 线程池隔离
  • 信号量隔离

SpringCloud集成Hystrix

  1. 导入相关依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

注意,因为Fegin中包含了Hystrix,如果使用了Fegin客户端请求框架,只需要在配置文件中设置feign.hystrix.enabled=true开启Hystrix即可。

  1. 在启动类上添加@EnableHystrix注解

服务降级

所谓服务降级就是说当服务迟迟没有响应,不会继续等待,而是会执行一个兜底的方法避免请求的阻塞。一定程度上降低了故障的发生。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解

@HystrixCommand(fallbackMethod = "error")
public String helloService(){
    return restTemplate.getForEntity("http://myservice/hello",String.class).getBody();
}

其中fallbackMethod属性为这个rest请求指定了一个失败的回调方法error()

public String error(){
    return "error";
}

紧接着在Controller层创建一个接口,并调用刚才的业务方法helloService()

@RequestMapping(value = "/consumer",method = RequestMethod.GET)
public String helloConsumer(){
    return helloService.helloService();
}

为了测试需要,将myservicehello接口做一些小小的修改:在方法中让他沉睡1000ms(这是因为Hystrix的默认超时时间为1000ms,达到了这个时间就会进行降级)

@GetMapping("/hello")
public String index() throws InterruptedException {
    client.getServices().forEach(System.out::println);
    Thread.sleep(1000);
    return "Hello world";
}

现在,启动注册中心、客户端、服务端,开始测试

服务降级测试-1.png

测试达到预期结果!

服务熔断

服务熔断跟服务降级很相似,不同的地方在于熔断表示会断开与服务的连接,不过这种断开一般是暂时的,后续还会尝试与服务进行连接,如果还是不行则继续保持熔断状态,反复如此。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并为其设置一些参数

@HystrixCommand(fallbackMethod = "error",commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "30")
})
public String helloService(){
    return restTemplate.getForEntity("http://myservice/hello",String.class).getBody();
}

其中circuitBreaker.requestVolumeThreshold表示请求数量、execution.isolation.thread.timeoutInMilliseconds表示请求超时时间、circuitBreaker.errorThresholdPercentage表示请求失败数所占请求数量的百分比。综合来看,大意就是:请求数量达到了2,且失败的请求数占其30%,则会熔断当前请求。

同样为了便于测试,将myservicehello接口做一些小小的修改:设置其睡眠时间为2000ms

@GetMapping("/hello")
public String index() throws InterruptedException {
    System.out.println(client.getServices().get(0)+"已收到请求");
    Thread.sleep(2000);
    return "Hello world";
}

测试,请求两次后的结果如下
熔断请求1-1.png

发现服务端只接受到了一次请求,这是因为在第二次请求时由于失败的请求比例已经大于等于阈值,Hystrix已经对服务做了熔断。但正如之前所述,熔断不是永久的,默认5000ms后会暂时解除熔断如果请求仍失败,则再次熔断。

5000ms后再次发起请求
熔断请求2-1.png

再次接收到请求

请求合并

将多个请求合并为一个请求进行服务的访问,能一定程度上减少网络开销

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCollapser注解,并对其属性进行设置。需要注意的是,这个方法中的代码不会被执行

@HystrixCollapser(batchMethod = "mybatch",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,collapserProperties = {
        @HystrixProperty(name = "maxRequestsInBatch",value = "200"),
        @HystrixProperty(name = "timerDelayInMilliseconds",value = "10"),
})
public Future<String> helloService2(String name){
    System.out.println("是否被执行?");
    return null;
}

其中,batchMethod属性表示执行合并后请求的方法;maxRequestsInBatch表示批处理最大请求数,就是说最大能合并多少条请求;timerDelayInMilliseconds指执行合并操作的毫秒数。需要注意的是方法的返回类型必须Future<T>类型。

创建一个mybatch方法,并挂上@HystrixCommand注解,这个方法中的参数、返回值是一个集合类型。这是因为多个参数被合并为一个集合,多个返回值也被合并为一个集合的原因

@HystrixCommand
public List<String> mybatch(List<String> names){
    List<String> list = restTemplate.postForObject("http://myservice/hello2",names,List.class);
    return list;
}

在Controller中新建一个接口,调用两次helloService2()方法,意为将这两个请求合并为一个请求

@RequestMapping(value = "/consumer2")
public String helloConsumer2() throws ExecutionException, InterruptedException {
    Future<String> result1 = helloService.helloService2("张三");
    Future<String> result2 = helloService.helloService2("李四");
    System.out.println(result1.get());
    System.out.println(result2.get());
    return "OK";
}

而在服务端,仍然要返回合并了多条数据的集合类型

@PostMapping("/hello2")
public List<String> index2(@RequestBody List<String> names) throws InterruptedException {
    System.out.println("已经接收到批处理请求");
    List<String> list = new ArrayList<>();
    list.add(names.get(0));
    list.add(names.get(1));
    return list;
}

打开浏览器访问/consumer2接口

批处理-1.png

服务端只接收到了一次请求,所以测试成功。

线程池隔离

默认情况下,web程序需要依赖tomcat的内置线程池发起服务的调用,这就导致程序中所有的请求都是使用同一个线程池,如果程序中存在一个请求占用的线程比较多,就会间接影响到其他的请求调用。通过为不同请求指定Hystrix的线程池可以实现线程池的隔离,使不同请求互不影响。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并对其属性进行设置。

@HystrixCommand(groupKey = "apple",threadPoolKey = "apple",threadPoolProperties = {
        @HystrixProperty(name = "coreSize",value = "4"),
})
public String thread1(){
    System.out.println(Thread.currentThread().getName());
    return "thread1";
}

public String thread2(){
    System.out.println(Thread.currentThread().getName());
    return "thread2";
}

其中groupKey表示分组名称、threadPoolKey表示线程池名称,HystrixProperty则用来指定线程数量。上面的示例为thread1指定了Hystrix线程池,thread2则使用tomcat线程池,从而实现请求之间互不影响。

在Controller中添加一个接口,分别调用thread1thread2

@RequestMapping("/thread")
public String threadPool(){
    helloService.thread1();
    helloService.thread2();
    return "OK";
}

打开浏览器访问/thread接口
线程池隔离-1.png

结果显示,thread1thread2分别使用了不同的线程池,不会互相影响。

信号量隔离

可以将信号量理解为线程计数器,假设1s内最多允许2条线程访问请求,第3条线程则会调用fallbackMethod指定的兜底方法。所以说信号量隔离实现了服务的限流。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并对其属性进行设置,并指定兜底方法

@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),
        @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "2")
},fallbackMethod = "down")
public String semaphore() throws InterruptedException {
    System.out.println("OK");
    Thread.sleep(900);
    return null;
}

其中,execution.isolation.strategy指定使用信号量的方式实现服务的降级(默认使用线程池),execution.isolation.semaphore.maxConcurrentRequests则表示1s内最多只能有2个线程访问服务。

创建一个降级方法

public String down(){
    System.out.println("执行降级");
    return null;
}

在Controller中添加一个新的接口

@RequestMapping("/semaphore")
public String semaphore() throws InterruptedException {
    helloService.semaphore();
    return "OK";
}

使用apache-jmeter工具进行请求测试。因为设置了信号量最多为2,所以这里指定3个请求线程
jmeter1-1.png

设置请求地址并发起请求
jmeter2-1.png
jmeter3-1.png

控制台打印信息:
semaphore-1.png

由于1s内只允许2个线程请求服务,所以当第3个发起请求会被降级处理。

谢谢大家

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个比较复杂的需求,需要涉及到多个方面的技术。下面我尝试给你提供一个基本的架构思路和代码实现示例,供你参考。 1. 系统功能服务设计 根据需求,我们需要实现两个系统功能服务,可以分别命名为ServiceA和ServiceB。为了方便演示,这里我们简化服务功能,ServiceA提供一个加法运算服务,ServiceB提供一个字符串反转服务。 ServiceA代码示例: ``` @RestController public class ServiceAController { @GetMapping("/add") public int add(@RequestParam("a") int a, @RequestParam("b") int b) { return a + b; } } ``` ServiceB代码示例: ``` @RestController public class ServiceBController { @GetMapping("/reverse") public String reverse(@RequestParam("str") String str) { return new StringBuilder(str).reverse().toString(); } } ``` 2. Eureka服务发现 为了实现服务之间的通信,我们可以使用Eureka技术实现服务发现和注册。Eureka是一个基于REST的服务,用于定位服务,以实现中间层服务器的负载平衡和故障转移。下面是Eureka服务端和客户端的代码示例。 Eureka服务端代码示例: ``` @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } } ``` Eureka客户端代码示例: ``` @SpringBootApplication @EnableDiscoveryClient public class ServiceAApplication { public static void main(String[] args) { SpringApplication.run(ServiceAApplication.class, args); } } ``` ``` @SpringBootApplication @EnableDiscoveryClient public class ServiceBApplication { public static void main(String[] args) { SpringApplication.run(ServiceBApplication.class, args); } } ``` 3. Hystrix服务容错保护和服务降级 为了保证系统的健壮性,我们可以使用Hystrix技术实现服务容错保护和服务降级。Hystrix是一个用于处理分布式系统的延迟和容错的库,它提供了保护和控制分布式系统间组件的交互。 下面是ServiceA服务Hystrix代码示例: ``` @RestController public class ServiceAController { @Autowired private ServiceBClient serviceBClient; @GetMapping("/add") @HystrixCommand(fallbackMethod = "addFallback") public int add(@RequestParam("a") int a, @RequestParam("b") int b) { return serviceBClient.reverse(a, b); } public int addFallback(int a, int b) { return -1; } } ``` 下面是ServiceB服务Hystrix代码示例: ``` @RestController public class ServiceBController { @GetMapping("/reverse") @HystrixCommand(fallbackMethod = "reverseFallback") public String reverse(@RequestParam("str") String str) { if (str == null || str.isEmpty()) { throw new IllegalArgumentException("String cannot be empty"); } return new StringBuilder(str).reverse().toString(); } public String reverseFallback(String str) { return ""; } } ``` 4. 网关路由功能 为了实现网关路由功能,我们可以使用Spring Cloud Gateway技术,它是Spring Cloud生态系统中的网关解决方案。它基于Spring Framework 5,Project Reactor和Spring Boot 2.0,可以用作Zuul的替代方案。 下面是网关路由配置文件示例: ``` spring: cloud: gateway: routes: - id: serviceA uri: lb://service-a predicates: - Path=/serviceA/** - id: serviceB uri: lb://service-b predicates: - Path=/serviceB/** ``` 5. 接口自动生成在线接口文档 为了方便对外提供服务接口,我们可以使用Swagger技术自动生成在线接口文档。Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化Restful风格的Web服务。 下面是Swagger配置示例: ``` @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example")) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Example API") .description("Example API reference for developers") .version("1.0") .build(); } } ``` 以上是一个基本的后端微服务架构代码示例,实现了多个功能模块,包括服务发现、服务容错保护和服务降级、网关路由和在线接口文档。由于每个公司的技术栈和业务需求不同,实际的架构实现可能会有所不同,需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值