SpringCloud 详解(九)

Hystrix(续一)

服务降级(服务生产者)

服务生产者超时或宕机了,服务消费者不能一直卡死等待,服务生产者就必须要有服务降级。

Controller 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package ml.guest997.Controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {
@GetMapping("/payment/ok/{id}")
public String paymentOK(@PathVariable("id") Integer id) {
String s = "当前线程:" + Thread.currentThread().getName() + " paymentOK id:" + id;
log.info("result:" + s);
return s;
}

@GetMapping("/payment/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") //超时2秒将调用 fallbackMethod
})
public String paymentTimeOut(@PathVariable("id") Integer id) {
String s = null;
try {
TimeUnit.MILLISECONDS.sleep(3000);
s = "当前线程:" + Thread.currentThread().getName() + " paymentTimeOut id:" + id + " 耗时3秒";
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("result:" + s);
return s;
}

public String paymentTimeOutHandler(Integer id) { //fallback 方法一定要与原方法参数一致
return "当前线程:" + Thread.currentThread().getName() + " 系统繁忙或者运行报错,请稍后再试。";
}
}

主启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ml.guest997;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix //启动 Hystrix
public class Payment5 {
public static void main(String[] args) {
SpringApplication.run(Payment5.class, args);
}
}

超时测试

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/timeout/997,会发现调用的是 fallback 方法并且显示的线程是 HystrixTimer。

程序错误测试

修改 Controller 层中的 paymentTimeOut 方法,如下图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/payment/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") //超时2秒将调用 fallbackMethod 中的方法
})
public String paymentTimeOut(@PathVariable("id") Integer id) {
String s = null;
// try {
// TimeUnit.MILLISECONDS.sleep(3000);
// s = "当前线程:" + Thread.currentThread().getName() + " paymentTimeOut id:" + id + " 耗时3秒";
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
s = String.valueOf(1/0); //程序错误
log.info("result:" + s);
return s;
}

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/timeout/997,会发现依旧调用的是 fallback 方法并且显示的线程是 hystrix。

服务降级(服务消费者)

服务生产者没问题,服务消费者自己出了故障或有自我要求(自己的等待时间小于服务生产者响应回来的时间),服务消费者就可以自己进行服务降级。

Controller 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package ml.guest997.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import ml.guest997.service.PaymentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderController {
@Resource
private PaymentService paymentService;

@GetMapping("/consumer/payment/ok/{id}")
String paymentOK(@PathVariable("id") Integer id) {
return paymentService.paymentOK(id);
}

@GetMapping("/consumer/payment/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
String paymentTimeOut(@PathVariable("id") Integer id) {
return paymentService.paymentTimeOut(id);
}

public String paymentTimeOutHandler(Integer id){
return "支付系统繁忙请稍后再试,或者自己运行出错请检查自己。";
}
}

主启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ml.guest997;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@EnableHystrix //启动 Hystrix
public class Order3 {
public static void main(String[] args) {
SpringApplication.run(Order3.class, args);
}
}

超时测试

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/timeout/997,会发现调用的是 fallback 方法。

程序错误测试

修改 Controller 层中的 paymentTimeOut 方法,如下图。

1
2
3
4
5
6
7
8
@GetMapping("/consumer/payment/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentTimeOut(@PathVariable("id") Integer id) {
// return paymentService.paymentTimeOut(id);
return String.valueOf(1/0);
}

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/timeout/997,会发现依旧调用的是 fallback 方法。

全局服务降级

每个业务方法对应一个降级方法,代码膨胀。我们希望的是特殊业务才有特殊的降级方法,平常的普通业务只需要个通用的降级方法即可。这样就避免了代码膨胀,合理减少了代码量。

Controller 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package ml.guest997.controller;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import ml.guest997.service.PaymentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "GlobalFallbackMethod") //注解表示全局的 fallback 方法
public class OrderController {
@Resource
private PaymentService paymentService;

@GetMapping("/consumer/payment/ok/{id}")
public String paymentOK(@PathVariable("id") Integer id) {
return paymentService.paymentOK(id);
}

@GetMapping("/consumer/payment/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
// })
@HystrixCommand //使用全局的 fallback 方法
public String paymentTimeOut(@PathVariable("id") Integer id) {
// return paymentService.paymentTimeOut(id);
return String.valueOf(10 / 0);
}

public String GlobalFallbackMethod() {
return "异常处理信息,请稍后再试。";
}

public String paymentTimeOutHandler(Integer id) {
return "支付系统繁忙请稍后再试,或者自己运行出错请检查自己。";
}
}

测试

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/timeout/997,会发现调用的是全局 fallback 方法。

通配服务降级(FeignFallback)

可以从上面的代码看到,Controller 层的代码有异常处理的代码,耦合度高。我们可以通过为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

添加配置

1
2
3
4
#启动 feign 断路器
feign:
circuitbreaker:
enabled: true

服务降级实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package ml.guest997.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentService {
@Override
public String paymentOK(Integer id) {
return "服务处理异常,请稍后再试。";
}

@Override
public String paymentTimeOut(Integer id) {
return "服务处理异常,请稍后再试。";
}
}

修改 Service 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package ml.guest997.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "PAYMENT", fallback = PaymentFallbackService.class) //使用该接口的实现类统一进行服务降级处理
public interface PaymentService {
@GetMapping("/payment/ok/{id}")
String paymentOK(@PathVariable("id") Integer id);

@GetMapping("/payment/timeout/{id}")
String paymentTimeOut(@PathVariable("id") Integer id);
}

测试

分别启动 Eureka-Server、Payment5 和 Order3 模块后,浏览器访问:127.0.0.1/consumer/payment/ok/997,会发现调用的是服务降级处理的实现类方法。