天天看點

服務容錯保護SpringCloud Hystrix服務容錯保護SpringCloud Hystrix

服務容錯保護SpringCloud Hystrix

文章目錄

  • 服務容錯保護SpringCloud Hystrix
    • 一、快速入門
    • 二、請求合并
    • 三、Hystrix 儀表盤
    • 四、Turbine 叢集監控

一、快速入門

eureka 9001

userservice 9003

userservice 9006

ribbonconsumer 9005

在 ribbonconsumer 中加入 pom 依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.5.RELEASE</version>
</dependency>
           

在 ribbonconsumer 工程的主類 SpringcloudRibbonConsumerApplication 中使用 @EnableCircuitBreaker 注解開啟斷路器功能

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class SpringcloudRibbonConsumerApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return  new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudRibbonConsumerApplication.class, args);
    }
}
                

還可以使用 SpringCloud 應用中的 @SpringCloudApplication 注解來修飾應用主類,該注解中包含了上述所引用的三個注解

@SpringCloudApplication
public class SpringcloudRibbonConsumerApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return  new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudRibbonConsumerApplication.class, args);
    }
}
                

改造服務消費方式,在 helloService 函數上增加 @HystrixCommand 注解來指定回調方法

@Service
public class HelloService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "helloFallback")
    public String helloService(){
        long start = System.currentTimeMillis();
        String result = restTemplate.exchange("http://SERIVCE-USER/hello", HttpMethod.GET, null, String.class).getBody();
        System.out.println("spend time :" + (System.currentTimeMillis() - start));
        return  result;
    }

    public String helloFallback(){
        return "error";
    }
}
                
@RestController
public class ConsumerController {
    @Autowired
    RestTemplate restTemplate;

    @Autowired
    HelloService helloService;

    @GetMapping(value = "ribbon-consumer")
    public Object helloConsumer(){
        return helloService.helloService();
        //return restTemplate.exchange("http://SERIVCE-USER/hello", HttpMethod.GET, null, String.class).getBody();
        //return restTemplate.getForEntity("http://SERIVCE-USER/hello",String.class).getBody();
    }
}
                

修改 userservice 9003

@RestController
@Slf4j
public class HelloController {

    @GetMapping(value = "/hello")
    public String hello() throws InterruptedException {
        int sleepTime = new Random().nextInt(3000);
        log.info("sleepTime:"+sleepTime);
        Thread.sleep(sleepTime);
        return "Hello World 9003";
    }
}
                

重新開機 userservice 9003 和 ribbonconsumer 9005 後,連續通路 http://localhost:9005/ribbon-consumer 幾次,當 userservice 9003 Spend time 大于 2000 的時候,就會傳回 error,即服務消費者因調用的服務潮濕進而觸發熔斷請求,并調用回調邏輯傳回結果。

Hystrix 預設逾時時間為 2000 毫秒

二、請求合并

Spring Cloud中Hystrix的請求合并

服務提供者 SERIVCE-USER 9003 修改

@GetMapping(value = "/getUserInfo/{id}")
    public User getUserInfo(@PathVariable String id){
        return new User("張三","123");
    }

    @GetMapping(value = "/getUserInfos/{ids}")
    public List<User> getUserInfos(@PathVariable String ids){
        System.out.println("getUserInfos---------"+ids+"Thread.currentThread().getName():" + Thread.currentThread().getName());
        List<User> list = new ArrayList<>();
        list.add(new User("張三","123"));
        list.add(new User("李四","1234"));
        list.add(new User("王五","12345"));
        return list;
    }
                

服務消費者

RIBBON-CONSUMER 9005

UserService

@Service
public class UserService {

    @Autowired
    RestTemplate restTemplate;

    public String getUserInfo(Long id) {
        return restTemplate.exchange("http://SERIVCE-USER/getUserInfo/1", HttpMethod.GET, null, String.class).getBody();
    }

    public String getUserInfos(List<Long> ids) {
        String usr = "http://SERIVCE-USER/getUserInfos/"+ids.toString();
        String result = restTemplate.exchange(usr, HttpMethod.GET, null, String.class).getBody();
        return result;
    }
}
                

UserBatchCommand 批處理指令,繼承自HystrixCommand,用來處理合并之後的請求

public class UserBatchCommand extends HystrixCommand<List<User>> {
    private List<Long> ids;

    @Autowired
    UserService userService;

    public UserBatchCommand(List<Long> ids, UserService userService) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CollapsingGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("CollapsingKey")));
        this.ids = ids;
        this.userService = userService;
    }

    @Override
    protected List<User> run(){
        String result = userService.getUserInfos(ids);
        List<User> users =  JSONObject.parseArray(result,User.class);
        return users;
    }
}
                

UserCollapseCommand 繼承自HystrixCollapser來實作請求合并

public class UserCollapseCommand extends HystrixCollapser<List<User>, User, Long> {
    private UserService userService;
    private Long id;

    public UserCollapseCommand(UserService userService, Long id) {
        super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)));
        this.userService = userService;
        this.id = id;
    }

    @Override
    public Long getRequestArgument() {
        return id;
    }

    @Override
    protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Long>> collapsedRequests) {
        List<Long> ids = new ArrayList<>(collapsedRequests.size());
        ids.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));
        UserBatchCommand userBatchCommand = new UserBatchCommand(ids, userService);
        return userBatchCommand;
    }

    @Override
    protected void mapResponseToRequests(List<User> batchResponse, Collection<CollapsedRequest<User, Long>> collapsedRequests) {
        System.out.println("mapResponseToRequests");
        int count = 0;
        for (CollapsedRequest<User, Long> collapsedRequest : collapsedRequests) {
            User user = batchResponse.get(count++);
            collapsedRequest.setResponse(user);
        }
    }
}
                
  • 1.首先在構造方法中,我們設定了請求時間窗為100ms,即請求時間間隔在100ms之内的請求會被合并為一個請求。
  • 2.createCommand方法主要用來合并請求,在這裡擷取到各個單個請求的id,将這些單個的id放到一個集合中,然後再建立出一個BookBatchCommand對象,用該對象去發起一個批量請求。
  • 3.mapResponseToRequests方法主要用來為每個請求設定請求結果。該方法的第一個參數batchResponse表示批處理請求的結果,第二個參數collapsedRequests則代表了每一個被合并的請求,然後我們通過周遊batchResponse來為collapsedRequests設定請求結果。

測試

@RequestMapping("/test7")
    public void getUserBatchTest() throws ExecutionException, InterruptedException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        UserCollapseCommand bc1 = new UserCollapseCommand(userService, 1l);
        UserCollapseCommand bc2 = new UserCollapseCommand(userService, 2l);
        UserCollapseCommand bc3 = new UserCollapseCommand(userService, 3l);
        UserCollapseCommand bc4 = new UserCollapseCommand(userService, 4l);
        Future<User> q1 = bc1.queue();
        Future<User> q2 = bc2.queue();
        Future<User> q3 = bc3.queue();
        User user1 = q1.get();
        User user2 = q2.get();
        User user3 = q3.get();
        Thread.sleep(3000);
        Future<User> q4 = bc4.queue();
        User user4 = q4.get();
        System.out.println("user1>>>"+user1);
        System.out.println("user2>>>"+user2);
        System.out.println("user3>>>"+user3);
        System.out.println("user4>>>"+user4);
        context.close();
    }
                
  • 1.首先要初始化 HystrixRequestContext
  • 2.建立 UserCollapseCommand 類的執行個體來發起請求,先發送3個請求,然後睡眠3秒鐘,再發起1個請求,這樣,前3個請求就會被合并為一個請求,第四個請求因為間隔的時間比較久,是以不會被合并,而是單獨建立一個線程去處理。

控制台輸出

getUserInfos---------[1, 2, 3]Thread.currentThread().getName():http-nio-9003-exec-2
getUserInfos---------[4]Thread.currentThread().getName():http-nio-9003-exec-3
           

通過注解實作請求合并

在UserService中添加兩個方法,如下:

@HystrixCollapser(batchMethod = "test11",collapserProperties = {@HystrixProperty(name ="timerDelayInMilliseconds",value = "100")})
    public Future<User> test10(Long id) {
        return null;
    }

    @HystrixCommand
    public List<User> test11(List<Long> ids) {
        String usr = "http://SERIVCE-USER/getUserInfos/"+ids.toString();
        String result = restTemplate.exchange(usr, HttpMethod.GET, null, String.class).getBody();
        List<User> users =  JSONObject.parseArray(result,User.class);
        return users;
    }
                

在test10方法上添加@HystrixCollapser注解實作請求合并,用batchMethod屬性指明請求合并後的處理方法,collapserProperties屬性指定其他屬性。

調用

@RequestMapping("/test8")
    @ResponseBody
    public void test8() throws ExecutionException, InterruptedException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        Future<User> f1 = userService.test10(1l);
        Future<User> f2 = userService.test10(2l);
        Future<User> f3 = userService.test10(3l);
        User b1 = f1.get();
        User b2 = f2.get();
        User b3 = f3.get();
        Thread.sleep(3000);
        Future<User> f4 = userService.test10(4l);
        User b4 = f4.get();
        System.out.println("b1>>>"+b1);
        System.out.println("b2>>>"+b2);
        System.out.println("b3>>>"+b3);
        System.out.println("b4>>>"+b4);
        context.close();
    }
                

執行結果同之前一樣

getUserInfos---------[1, 3, 2]Thread.currentThread().getName():http-nio-9003-exec-1
getUserInfos---------[4]Thread.currentThread().getName():http-nio-9003-exec-2
           

三、Hystrix 儀表盤

解決Hystrix Dashboard 一直是Loading …的情況

Hystrix儀表盤監控單執行個體節點

spring cloud2 hystrix沒有hystrix.stream路徑解決方案

建構 hystrix-dashboard 9007

pom 依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zk.springcloud</groupId>
    <artifactId>springcloud-hystrix-dashboard</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springcloud-hystrix-dashboard</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

為應用主類加上 @EnableHystrixDashboard , 啟用 Hystrix Dashboard 功能

spring.application.name=hystrix-dashboard
server.port=9007
           

啟動該應用,通路 http://localhost:9007/hystrix/ 可以看到 Hystrix Dashboard

在 ribbon-consumer 9005 的 dependencies 節點新增 spring-boot-starter-actuator 監控子產品以開啟監控相關的端點,并確定已經引入斷路器的依賴

spring-cloud-starter-hystrix

<dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-hystrix</artifactId>
           <version>1.4.5.RELEASE</version>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
           

在 ribbon-consumer 9005 確定在服務執行個體的主類中已經使用 @EnableCircuitBreaker 注解,開啟了斷路器功能,在啟動類中加入如下代碼

@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;
}
                

這樣就可以直接使用 http://localhost:9005/hystrix.stream 檢視監控資訊

重新開機 ribbon-consumer ,在 Hystrix Dashboard 的首頁輸入 http://localhost:9005/hystrix.stream 就是用于 Hystrix Dashboard 來占線監控資訊的接口

四、Turbine 叢集監控

Spring Cloud:Turbine監控資料聚合【Finchley 版】Could not initiate connection to host, giving up: [xxx]

建立 turbine 項目

pom 依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zk.springcloud.turbie</groupId>
    <artifactId>springcloud-turbie</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springcloud-turbie</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
            <version>2.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

在 application.properties 中加入 Eureka 和 Turbine 的相關配置

spring.application.name=turbie
server.port=9008
management.server.port=9009

eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9004/eureka/

turbine.app-config=RIBBON-CONSUMER
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
turbine.instanceUrlSuffix.default = /hystrix.stream
           

通路 http://localhost:9008/hystrix/ 開啟對 http://localhost:9008/turbine.stream 的監控

繼續閱讀