Spring Cloud Alibaba之服務容錯組件 – Sentinel擴展(十九)

  • 2019 年 11 月 2 日
  • 筆記

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/pyycsd/article/details/102803281

擴展 – 錯誤資訊優化

Sentinel默認在當前服務觸發限流或降級時僅返回簡單的異常資訊,如下:

Spring Cloud Alibaba之服務容錯組件 – Sentinel [程式碼篇]

並且限流和降級返回的異常資訊是一樣的,導致無法根據異常資訊區分是觸發了限流還是降級。

所以我們需要對錯誤資訊進行相應優化,以便可以細緻區分觸發的是什麼規則。Sentinel提供了一個UrlBlockHandler介面,實現該介面即可自定義異常處理邏輯。具體如下示例:

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;  import com.alibaba.csp.sentinel.slots.block.BlockException;  import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;  import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;  import com.alibaba.csp.sentinel.slots.block.flow.FlowException;  import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;  import com.alibaba.csp.sentinel.slots.system.SystemBlockException;  import lombok.Builder;  import lombok.Data;  import lombok.extern.slf4j.Slf4j;  import org.codehaus.jackson.map.ObjectMapper;  import org.springframework.http.MediaType;  import org.springframework.stereotype.Component;    import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;  import java.io.IOException;    /**   * 自定義流控異常處理   **/  @Slf4j  @Component  public class MyUrlBlockHandler implements UrlBlockHandler {        @Override      public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {          MyResponse errorResponse = null;          // 不同的異常返回不同的提示語          if (e instanceof FlowException) {              errorResponse = MyResponse.builder()                      .status(100).msg("介面限流了")                      .build();          } else if (e instanceof DegradeException) {              errorResponse = MyResponse.builder()                      .status(101).msg("服務降級了")                      .build();          } else if (e instanceof ParamFlowException) {              errorResponse = MyResponse.builder()                      .status(102).msg("熱點參數限流了")                      .build();          } else if (e instanceof SystemBlockException) {              errorResponse = MyResponse.builder()                      .status(103).msg("觸發系統保護規則")                      .build();          } else if (e instanceof AuthorityException) {              errorResponse = MyResponse.builder()                      .status(104).msg("授權規則不通過")                      .build();          }            response.setStatus(500);          response.setCharacterEncoding("utf-8");          response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);          new ObjectMapper().writeValue(response.getWriter(), errorResponse);      }  }    /**   * 簡單的響應結構體   */  @Data  @Builder  class MyResponse {      private Integer status;      private String msg;  }

此時再觸發流控規則就可以響應程式碼中自定義的提示資訊了:

擴展 – 實現區分來源

當配置流控規則或授權規則時,若需要針對調用來源進行限流,得先實現來源的區分,Sentinel提供了RequestOriginParser介面來處理來源。只要Sentinel保護的介面資源被訪問,Sentinel就會調用RequestOriginParser的實現類去解析訪問來源。

寫程式碼:首先,服務消費者需要具備有一個來源標識,這裡假定為服務消費者在調用介面的時候都會傳遞一個origin的header參數標識來源。具體如下示例:

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;  import com.alibaba.nacos.client.utils.StringUtils;  import lombok.extern.slf4j.Slf4j;  import org.springframework.stereotype.Component;    import javax.servlet.http.HttpServletRequest;    /**   * 實現區分來源   **/  @Slf4j  @Component  public class MyRequestOriginParser implements RequestOriginParser {        @Override      public String parseOrigin(HttpServletRequest request) {          // 從header中獲取名為 origin 的參數並返回          String origin = request.getHeader("origin");          if (StringUtils.isBlank(origin)) {              // 如果獲取不到,則拋異常              String err = "origin param must not be blank!";              log.error("parse origin failed: {}", err);              throw new IllegalArgumentException(err);          }            return origin;      }  }

編寫完以上程式碼並重啟項目後,此時header中不包含origin參數就會報錯了:

image.png

擴展 – RESTful URL支援

了解過RESTful URL的都知道這類URL路徑可以動態變化,而Sentinel默認是無法識別這種變化的,所以每個路徑都會被當成一個資源,如下圖:

這顯然是有問題的,好在Sentinel提供了UrlCleaner介面解決這個問題。實現該介面可以讓我們對來源url進行編輯並返回,這樣就可以將RESTful URL里動態的路徑轉換為佔位符之類的字元串。具體實現程式碼如下:

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;  import lombok.extern.slf4j.Slf4j;  import org.apache.commons.lang3.math.NumberUtils;  import org.springframework.stereotype.Component;    import java.util.Arrays;    /**   * RESTful URL支援   **/  @Slf4j  @Component  public class MyUrlCleaner implements UrlCleaner {        @Override      public String clean(String originUrl) {          String[] split = originUrl.split("/");            // 將數字轉換為特定的佔位標識符          return Arrays.stream(split)                  .map(s -> NumberUtils.isNumber(s) ? "{number}" : s)                  .reduce((a, b) -> a + "/" + b)                  .orElse("");      }  }

此時該RESTful介面就不會像之前那樣一個數字就註冊一個資源了: