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介面就不會像之前那樣一個數字就註冊一個資源了: