Spring Cloud(5):Hystrix的使用

熔斷:相似生活中的保險絲,電流過大就會熔斷html

降級:相似生活中的旅行,行李箱只有那麼大,因此要拋棄一些非必需的物品java

 

熔斷降級應用:web

某寶雙十一商品下單,用戶量巨大,因而考慮拋棄相關商品推薦等模塊,確保該商品信息和下單功能通暢redis

 

熔斷和降級的區別以及聯繫:spring

1.二者都是爲了防止系統崩潰,提升可用性apache

2.最終給用戶的體驗是某些功能暫時不可用api

3.服務熔斷通常是由下游服務故障致使的,服務降級通常是從系統總體負荷考慮,由調用方控制緩存

 

Hystrix的使用:app

基於上一篇Feign的使用:http://www.javashuo.com/article/p-wdnkmplt-mx.htmlide

訂單服務Order-Service中加入依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

在啓動類中加入註解

package org.dreamtech.orderservice;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class OrderServiceApplication {

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

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

Controller層進行修改

package org.dreamtech.orderservice.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.dreamtech.orderservice.service.ProductOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/order")
public class OrderController {
    private final ProductOrderService productOrderService;

    @Autowired
    public OrderController(ProductOrderService productOrderService) {
        this.productOrderService = productOrderService;
    }

    @RequestMapping("/save")
    @HystrixCommand(fallbackMethod = "saveOrderFail")
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

    private Object saveOrderFail(int userId, int productId) {
        Map<String, Object> msg = new HashMap<>();
        msg.put("code", -1);
        msg.put("msg", "請稍後重試");
        return msg;
    }
}

注意:saveOrderFail方法的參數必須和save的參數一致

 

依次啓動Eureka-Server->Product-Service->Order-Service

 

訪問:http://localhost:8781/api/order/save?user_id=1&product_id=4

正常顯示:

{"code":0,"data":{"id":0,"productName":"\"iPhone4 data from port=8771\"","tradeNo":"13933d59-34fd-473a-8dbe-f114bc9fd2b9","price":4444,"createTime":"2019-05-17T03:54:12.682+0000","userId":1,"userName":null}}

 

這時候我關閉Product-Service,模擬熔斷

再次訪問:http://localhost:8781/api/order/save?user_id=1&product_id=4

顯示:

{"msg":"請稍後重試","code":-1}

 

說明Hystrix配置和使用成功!

 

Feign結合Hystrix的使用:

配置Feign容許Hystrix

feign:
  hystrix:
    enabled: true

FeignClient加入fallback處理

package org.dreamtech.orderservice.service;

import org.dreamtech.orderservice.fallback.ProductClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "product-service",fallback = ProductClientFallback.class)
public interface ProductClient {
    @RequestMapping("/api/product/find")
    String findById(@RequestParam(value = "id")int id);
}

ProductClientFallback

package org.dreamtech.orderservice.fallback;

import org.dreamtech.orderservice.service.ProductClient;
import org.springframework.stereotype.Component;

@Component
public class ProductClientFallback implements ProductClient {
    @Override
    public String findById(int id) {
        System.out.println("Feign調用Product-Service異常");
        return null;
    }
}

啓動項目,訪問:http://localhost:8781/api/order/save?user_id=1&product_id=3成功

關閉Product-Service,訪問http://localhost:8781/api/order/save?user_id=1&product_id=3

顯示

{"msg":"請稍後重試","code":-1}

而且在控制檯打印

Feign調用Product-Service異常

 

服務異常報警通知:

只是熔斷和降級是不夠了,還須要報警機制

因爲訪問量較大,要作到只報警一次,那麼須要進行標識

一般實現原理基於緩存,好比Redis

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

配置

spring:
  application:
    name: order-service
  redis:
    host: 127.0.0.1
    database: 0
    port: 6379
    timeout: 2000

代碼

package org.dreamtech.orderservice.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.apache.commons.lang.StringUtils;
import org.dreamtech.orderservice.service.ProductOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/order")
public class OrderController {
    private final ProductOrderService productOrderService;

    private final StringRedisTemplate redisTemplate;

    @Autowired
    public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) {
        this.productOrderService = productOrderService;
        this.redisTemplate = redisTemplate;
    }

    @RequestMapping("/save")
    @HystrixCommand(fallbackMethod = "saveOrderFail")
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

    private Object saveOrderFail(int userId, int productId) {

        String saveOrderKey = "save-order";
        String sendValue = redisTemplate.opsForValue().get(saveOrderKey);

        new Thread(() -> {
            if (StringUtils.isBlank(sendValue)) {
                // TODO 因爲沒有短信發送接口,模擬發送短信
                System.out.println("短信發送成功");
                redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
            } else {
                System.out.println("已經發送太短信,20秒內不重複發送");
            }
        }).start();


        Map<String, Object> msg = new HashMap<>();
        msg.put("code", -1);
        msg.put("msg", "請稍後重試");
        return msg;
    }
}

 

測試

啓動項目,訪問http://localhost:8781/api/order/save?user_id=1&product_id=3沒有問題

關閉Product-Service再訪問,控制檯打印:

Feign調用Product-Service異常
短信發送成功

再次訪問:

Feign調用Product-Service異常
已經發送太短信,20秒內不重複發送

等待20秒訪問:

Feign調用Product-Service異常
短信發送成功

成功

 

或者更進一步,拿到IP,發送短信

package org.dreamtech.orderservice.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.apache.commons.lang.StringUtils;
import org.dreamtech.orderservice.service.ProductOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/order")
public class OrderController {
    private final ProductOrderService productOrderService;

    private final StringRedisTemplate redisTemplate;

    @Autowired
    public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) {
        this.productOrderService = productOrderService;
        this.redisTemplate = redisTemplate;
    }

    @RequestMapping("/save")
    @HystrixCommand(fallbackMethod = "saveOrderFail")
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

    private Object saveOrderFail(int userId, int productId,HttpServletRequest request) {

        String saveOrderKey = "save-order";
        String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
        String ip = request.getRemoteAddr();

        new Thread(() -> {
            if (StringUtils.isBlank(sendValue)) {
                // TODO 因爲沒有短信發送接口,模擬發送短信
                System.out.println("短信發送成功,緊急狀況,用戶下單失敗!來自於IP:"+ip);
                redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
            } else {
                System.out.println("已經發送太短信,20秒內不重複發送");
            }
        }).start();


        Map<String, Object> msg = new HashMap<>();
        msg.put("code", -1);
        msg.put("msg", "請稍後重試");
        return msg;
    }
}

 

Hystrix自定義配置:

關閉超時時間(實際狀況不要用)

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false

修改超時時間爲4秒:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000

不過Feign默認超時時間少於4秒,因此對Feign也進行修改:

feign:
  hystrix:
    enabled: true
  client:
    config:
      default:
        connectTimeout: 4000
        readTimeout: 4000

 感受YML的配置真的不舒服,不如繼續用PROPERTIES

 

Hystrix-Dashboard:對Hystrix過程和結果的可視化界面

 

Hystrix-Dashboard的使用:

加入依賴:不僅是Dashboard,還依賴於SpringBoot的監控Actuator

        <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-actuator</artifactId>
        </dependency>

 

在啓動類中加入註解:@EnableHystrixDashboard

package org.dreamtech.orderservice;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
@EnableHystrixDashboard
public class OrderServiceApplication {

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

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

 

啓動後訪問:http://localhost:8781/hystrix

根據提示輸入http://localhost:8781/actuator/hystrix.stream,發現報錯:

Unable to connect to Command Metric Stream.

並且我如我直接訪問http://localhost:8781/actuator/hystrix.stream也會顯示404

 

解決:配置文件

management:
  endpoints:
    web:
      exposure:
        include: "*"

緣由:配置Actuator開啓所有端點

 

啓動後訪問:http://localhost:8781/hystrix

根據提示輸入http://localhost:8781/actuator/hystrix.stream

 

或者直接訪問:http://localhost:8781/actuator/hystrix.stream不會報錯

相關文章
相關標籤/搜索