1,工作原理
2,常見規則
流量控制(FlowRule)
1,閾值類型設置如下
單機均攤:每個機器均攤,比如閾值填5,三個機器,就個機器都可以有5個
總體閾值:所有機器總閾值,比如閾值填5,三個機器,總共進5個請求
2,流控模式設置如下
只有流控效果是快速失敗才支持調整流控模式?
直接:默認,限制請求直接對資源進行限制
關聯:數據庫讀寫,當寫比較大時,限制讀的限流,達到優先寫
關聯測試案例
先添加倆方法,一個讀一個寫,在orderControoler類中
@GetMapping("/readDb")public String readDb(){return "readDb.....";}@GetMapping("/writeDb")public String writeDb(){return "writeDb.....";}
然后調用倆接口,在dashborard中查看倆資源,進行如下設置,即在讀的資源下設置限流,流控模式為關聯,關聯資源為writeDb,則可實現寫量過大時,訪問讀會被限流(設置好之后,測試先訪問readDb,正常訪問也沒被限流,但再訪問writeDb的時候,瘋狂刷新,再回去訪問readDb則readDb就會被限流)
鏈路: 根據不同的調用鏈,來控制不同調用鏈
鏈路測試案例
添加一個seckill方法,同createOrder,在orderController類中
@GetMapping("/create")public Order createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId){Order order = orderService.createOrder(userId,productId);return order;}@GetMapping("/seckill")public Order seckill(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId){Order order = orderService.createOrder(userId,productId);order.setId(999l);return order;}
yml配置文件添加配置,取消統一上下文
spring:cloud:sentinel:web-context-unify: false #是否統一web上下文(默認true)
配置鏈路規則:需要對createOrder限流,則如下設置,設置完之后測試,分別測試create接口和seckill接口,則會發現create接口隨便刷新無誤,seckill接口就有了一秒一個的限制
3,流控效果設置如下
快速失敗:(默認)如果沒有超閾值,就交給業務處理,如果超過了,多余的請求就會拋出blockHandler異常(比如每秒一個,哪么這一秒其他多的請求會被直接丟棄)
Warm Up:leng'q如果預見超高峰流量即將到達,設置參數參數QPS(每秒通過幾個請求),period(冷啟動周期),流程比如QPS=10,period=3,請求是一個穩步上升的趨勢,而不是突然增加(每秒多余的請求會被丟棄)
排隊等待:勻速排隊,比如QPS=2,則每秒會通過2個請求,但其他請求不會被丟棄,而是在后面排隊,等前面的請求,排隊也有最大等待時間,timeout=100,超過最大等待時間也會被丟棄,如下每秒2個請求
熔斷降級(DegradeRule)
思想:如下圖的復雜調用關系,A->G->D,B->F->D,如果此時D不穩定,一直不返回結果,那么如果G一直去等D的結果,D也會卡住,同樣A也會卡住,此時我們就需要及時切斷這種不穩定的調用,當G感知到D調用很慢之后,后面就采取措施不調用或者快速返回,切斷跟D的聯系,快速返回錯誤,整個鏈路才會快速結束,請求不積壓,則不會產生服務雪崩問題
此中間就有一個斷路器的概念,當調用的B是穩定通暢的,則斷路器是關閉的,一旦B出現了問題,則A就會打開,怎么知道什么時候該打開呢,此時就會有一個半開狀態,就是稍微開一下,測一下當前狀態如果調用不慢了,則閉合,如果調用仍然慢,則打開
斷路器工作原理如下:
首先默認斷路器是關閉的,調用是通的,此時我們設置慢調用比例(熔斷策略:滿調用比例/異常比例/異常數),比如設置0.7,就是請求70%都是慢請求(慢調用設置時長閾值)的話,就打開斷路器,調用就會失敗,但也不能一直打開,此時就會有一個熔斷時長,比如30s,這30s以內的所有請求不調用,直接返回失敗,但30s之后熔斷窗口就結束,結束后斷路器就會變成半開狀態,A需要調用B就會放一個請求過去探測一下,調用成功,則B又可靠了,斷路器閉合,如果還是調用失敗或者慢,則斷路器還是打開
熔斷策略測試案例
慢調用比例
首先運行Order和Product服務,然后給CreateOrder設置熔斷規則如下
修改一下商品服務,加個睡眠時長2秒,達到慢請求要求
在productController中的方法里加入睡眠時長
@GetMapping("/getProductById/{productId}")public Product getProductById(@PathVariable("productId") Long productId, HttpServletRequest request) {String header = request.getHeader("X-Token");System.out.println("product==="+header);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}Product product = productService.getProductById(productId);return product;}
}
然后測試,調用createOrder接口, 先5秒內刷新超過5個請求吧,開始的請求會比較慢,5秒后就會很快,應為斷路器打開了,就不再調用了,后臺也不再打印,order服務就會返回兜底回調,30秒之后,在測試刷新接口,會有一個慢請求之后,又會變快,因為半開測試之后,斷路器仍舊打開了,選擇不調用。去掉getProductById里面的睡眠,在重啟商品服務,熔斷規則還在,然后再測試接口調用,則正常調用,因為不存在慢調用問題
異常比例
同上構造異常調用
修改一下商品服務,加一個異常
在productController中的方法里加入異常代碼
@GetMapping("/getProductById/{productId}")public Product getProductById(@PathVariable("productId") Long productId, HttpServletRequest request) {String header = request.getHeader("X-Token");System.out.println("product==="+header);
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }int a=10/0;Product product = productService.getProductById(productId);return product;}
然后同上測試方法進行測試看效果
當沒有加熔斷的時候通過后天打印可以看出,每次A調用B 之后,都會返回異常,但是每次還是會調用,加了熔斷規則之后,達到規則之后,30s內就不會再調用,就直接執行兜底回調,不用再走一遍失敗的路
異常數
測試也同上面一樣
系統保護(SystemRule)
來源訪問控制(AuthorityRule)
熱點參數(ParamFlowRule)
類似流控規則,只是更細話到參數上面的限制
測試案例如下
場景1:每個用戶秒殺QPS不得超過1(秒殺下單userId級別)
我們可以先寫一個資源,在orderController中
//添加上sentinel資源注解@GetMapping("/seckill")@SentinelResource(value = "seckill-order",blockHandler = "seckillFallback")public Order seckill(@RequestParam(value = "userId",required = false) Long userId, @RequestParam(value = "productId",required = false) Long productId){Order order = orderService.createOrder(userId,productId);order.setId(999l);return order;}public Order seckillFallback(Long userId, Long productId, BlockException e){Order order = new Order();order.setId(-1l);order.setTotalAmount(BigDecimal.ZERO);order.setAddress("異常信息-----");return order;}
啟動訂單服務,設置熱點參數規則?
有參數userId,快速刷新效果,會限制1秒一個
去掉userId,快速刷新效果,不會限制
場景2:6號是vip用戶,需要放行,不限制QPS
場景3:666號商品已經下架,不可訪問
此時需要對另外一個參數進行限流,則需要再加一個限流規則
3,基礎場景
1,下載sentinel-dashboard控制臺
https://github.com/alibaba/Sentinel/releases/tag/1.8.8
Sentinel-Dashboard?是?阿里巴巴?開源的?流量控制組件?Sentinel的?可視化控制臺?,主要用于?微服務架構?的流量治理,支持實時監控、動態配置規則、熔斷降級等功能
下載好之后,在文件夾中cmd,開啟命令模式
然后輸入java -jar sentinel-dashboard-1.8.8.jar啟動
啟動之后,瀏覽器輸入http://localhost:8080/? ?即可訪問,賬密sentinel sentinel
2,添加配置
spring:cloud:sentinel:transport:dashboard: localhost:8080 #dashboard控制臺eager: true #提前加載(本來是訪問了請求才會加載,此時項目啟動就會連接加載)
3,啟動服務之后,查看控制臺
4,在OrderServiceImpl得createOrder方法上添加注解@SentinelResource(value = "createOrder")
@SentinelResource(value = "createOrder")@Overridepublic Order createOrder(Long userId, Long productId) {
// Product product = getProductByRemoteWithLoadBalancerByAnnotatin(productId);Product product = productFeignClient.getProductById(productId);Order order = new Order();order.setId(1l);//todo 遠程調用order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("愛學習");order.setAddress("北京");//todo 遠程調用order.setProductList(Arrays.asList(product));return order;}
5,重新啟動訂單服務,然后調用create接口,查看控制臺?
6,測試規則---流控
7,然后調用createorder接口,一秒一刷則無妨,但一秒超過一次請求就會報錯
4,異常處理
如上會默認返回一個流控錯誤字符串,我們需要返回json數據,返回錯誤消息和數據則就需要異常處理機制,常見得集中異常和處理方式如下:
1,Web接口
-----實現:AbstractSentinelInterceptor
異常處理,我們自己寫個異常處理類,實現BlockExceptionHandler,添加到容器
新增類MyBlockExceptionHandler?
package org.example.order.exception;import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.common.R;
import org.springframework.stereotype.Component;import java.io.PrintWriter;@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,String resourceName, BlockException e) throws Exception {response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();R error = R.error(500, resourceName + "被Sentinel限制了" + e.getClass());//寫出jsonString s = objectMapper.writeValueAsString(error);writer.write(s);writer.flush();writer.close();}
}
新增一個異常已處理對象R
在公共model模塊添加對象
package org.example.common;import lombok.Data;@Data
public class R {private Integer code;private String msg;private Object data;public static R ok(){R r = new R();r.setCode(200);return r;}public static R ok(Object data, String msg){R r = new R();r.setCode(200);r.setData(data);r.setMsg(msg);return r;}public static R error(){R r = new R();r.setCode(500);return r;}public static R error(Integer code, String msg){R r = new R();r.setCode(code);r.setMsg(msg);return r;}}
然后重新啟動,在dashboard中加入流控規則,調用接口,快速刷新即可看到以下報錯
2,@SentinelResource
------實現:SentinelResourceAspect
@SentinelResource一般標注在非controller層,一旦違反規則,如果業務規定有回調數據,那就用blockHandle去指定兜底回調,如果沒有指定回調,就讓異常拋給全局(springboot全局異常處理器),此注解得處理(SentinelResourceAspect)
回調處理
1,在方法上加上注解
?@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
2,添加回調方法
加上回調得方法,方法名通上面得注解里面blockHandler,其他內容需通上方注解方法,public、返回、參數
OrderServiceImpl
//增加回調createOrderFallback@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")@Overridepublic Order createOrder(Long userId, Long productId) {
// Product product = getProductByRemoteWithLoadBalancerByAnnotatin(productId);Product product = productFeignClient.getProductById(productId);Order order = new Order();order.setId(1l);//todo 遠程調用order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("愛學習");order.setAddress("北京");//todo 遠程調用order.setProductList(Arrays.asList(product));return order;}//同一個類中,加上回調得方法,方法名通上面得注解里面blockHandler,其他內容需通上方注解方法,public、返回、參數public Order createOrderFallback(Long userId, Long productId, BlockException e) {Order order = new Order();order.setId(0l);order.setTotalAmount(BigDecimal.ZERO);order.setUserId(userId);order.setNickName("未知用戶");order.setAddress("異常信息:"+e.getClass());return order;}
添加流控規則
測試快速刷新的異常處理結果
3,OpenFeign調用
-----實現:SentinelFeignAutoConfiguration
在openFeign筆記中提到過兜底回調,此處即用
首先編寫一個兜底返回org.example.order.feign.fallback.ProductFeignClientFallback?
實現ProductFeignClient 接口
package org.example.order.feign.fallback;
import java.math.BigDecimal;import org.example.order.feign.ProductFeignClient;
import org.example.producet.domain.Product;
import org.springframework.stereotype.Component;@Component
public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProductById(Long id) {Product product = new Product();product.setId(id);product.setPrice(new BigDecimal("0"));product.setProducetName("兜底回調的數據");product.setNum(0);return product;}
}
在ProductFeignClient 接口中添加fallback = ProductFeignClientFallback.class,這時,在遠程調用成功則不管,如果調用失敗,則會調用fallback,返回ProductFeignClientFallback里面編寫的默認數據
package org.example.order.feign;import org.example.order.feign.fallback.ProductFeignClientFallback;
import org.example.producet.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;//需要調用的服務的名字
@FeignClient(value = "services-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {@GetMapping("/getProductById/{id}")Product getProductById(@PathVariable("id") Long id);}
添加流控規則
測試異常回調處理結果
同樣,如果此處沒有些回調函數,就會拋給springboot全局異常處理
4,Sphu硬編碼?
以上三種異常處理的源碼都會有的編碼
SphU.entry(resourceName);
我們也可自定義比如?
try {Entry hahah = SphU.entry("hahah");order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("愛學習");order.setAddress("北京");} catch (BlockException e) {throw new RuntimeException(e);}