前言
之前我們聊了一下聊聊如何實現一個帶有攔截器功能的SPI。當時我們實現的核心思路是利用了責任鏈+動態代理。今天我們再聊下通過動態代理如何去整合sentinel實現熔斷限流
前置知識
alibaba sentinel簡介
Sentinel 是面向分散式服務架構的流量控制元件,主要以流量為切入點,從限流、流量整形、熔斷降級、系統負載保護、熱點防護等多個維度來幫助開發者保障微服務的穩定性。
sentinel工作流程
sentinel關鍵詞
資源 + 規則
sentinel實現模板套路
Entry entry = null;
// 務必保證 finally 會被執行
try {
// 資源名可使用任意有業務語義的字串,注意數目不能太多(超過 1K),超出幾千請作為引數傳入而不要直接作為資源名
// EntryType 代表流量型別(inbound/outbound),其中系統規則只對 IN 型別的埋點生效
entry = SphU.entry("自定義資源名");
// 被保護的業務邏輯
// do something...
} catch (BlockException ex) {
// 資源訪問阻止,被限流或被降級
// 進行相應的處理操作
} catch (Exception ex) {
// 若需要配置降級規則,需要通過這種方式記錄業務異常
Tracer.traceEntry(ex, entry);
} finally {
// 務必保證 exit,務必保證每個 entry 與 exit 配對
if (entry != null) {
entry.exit();
}
}
sentinel wiki
https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5
實現思路
整體實現思路:動態代理 + sentinel實現套路模板
核心程式碼
動態代理部分
1、定義動態代理介面
public interface CircuitBreakerProxy {
Object getProxy(Object target);
Object getProxy(Object target,@Nullable ClassLoader classLoader);
}
2、定義JDK或者cglib具體動態實現
以jdk動態代理為例子
public class CircuitBreakerJdkProxy implements CircuitBreakerProxy, InvocationHandler {
private Object target;
@Override
public Object getProxy(Object target) {
this.target = target;
return getProxy(target,Thread.currentThread().getContextClassLoader());
}
@Override
public Object getProxy(Object target, ClassLoader classLoader) {
this.target = target;
return Proxy.newProxyInstance(classLoader,target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CircuitBreakerInvocation invocation = new CircuitBreakerInvocation(target,method,args);
try {
return new CircuitBreakerInvoker().proceed(invocation);
//用InvocationTargetException包裹是java.lang.reflect.UndeclaredThrowableException問題
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
3、動態代理具體呼叫
public class CircuitBreakerProxyFactory implements ProxyFactory{
@Override
public Object createProxy(Object target) {
if(target.getClass().isInterface() || Proxy.isProxyClass(target.getClass())){
return new CircuitBreakerJdkProxy().getProxy(target);
}
return new CircuitBreakerCglibProxy().getProxy(target);
}
}
ps: 上面的動態代理實現思路是參考spring aop動態代理實現
具體參考類如下
org.springframework.aop.framework.AopProxy
org.springframework.aop.framework.DefaultAopProxyFactory
sentinel實現部分
public class CircuitBreakerInvoker {
public Object proceed(CircuitBreakerInvocation circuitBreakerInvocation) throws Throwable {
Method method = circuitBreakerInvocation.getMethod();
if ("equals".equals(method.getName())) {
try {
Object otherHandler = circuitBreakerInvocation.getArgs().length > 0 && circuitBreakerInvocation.getArgs()[0] != null
? Proxy.getInvocationHandler(circuitBreakerInvocation.getArgs()[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
Object result = null;
String contextName = "spi_circuit_breaker:";
String className = ClassUtils.getClassName(circuitBreakerInvocation.getTarget());
String resourceName = contextName + className + "." + method.getName();
Entry entry = null;
try {
ContextUtil.enter(contextName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, circuitBreakerInvocation.getArgs());
result = circuitBreakerInvocation.proceed();
} catch (Throwable ex) {
return doFallBack(ex, entry, circuitBreakerInvocation);
} finally {
if (entry != null) {
entry.exit(1, circuitBreakerInvocation.getArgs());
}
ContextUtil.exit();
}
return result;
}
}
ps: 如果心細的朋友,就會發現這個邏輯就是sentinel原生的實現套路邏輯
示例演示
示例準備
1、定義介面實現類並加上熔斷註解
@CircuitBreakerActivate(spiKey = "sqlserver",fallbackFactory = SqlServerDialectFallBackFactory.class)
public class SqlServerDialect implements SqlDialect {
@Override
public String dialect() {
return "sqlserver";
}
}
ps: @CircuitBreakerActivate這個是自定義熔斷註解,用過springcloud openfeign的@FeignClient註解大概就會有種熟悉感,都是在註解上配置fallbackFactory或者fallbackBack
2、定義介面熔斷工廠
@Slf4j
@Component
public class SqlServerDialectFallBackFactory implements FallbackFactory<SqlDialect> {
@Override
public SqlDialect create(Throwable ex) {
return () -> {
log.error("{}",ex);
return "SqlServerDialect FallBackFactory";
};
}
}
ps: 看這個是不是也很熟悉,這不就是springcloud hystrix的熔斷工廠嗎
3、專案中配置sentinel dashbord地址
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
filter:
enabled: false
示例驗證
1、瀏覽器訪問http://localhost:8082/test/ci...
此時訪問正常,看下sentinel-dashbord
2、配置限流規則
3、再次快速訪問
由圖可以看出已經觸發熔斷
本示例專案,如果不配置fallback或者fallbackFactory,當觸發限流時,它也會有個預設fallback
把示例註解的fallbackFactory去掉,如下
@CircuitBreakerActivate(spiKey = "sqlserver")
public class SqlServerDialect implements SqlDialect {
@Override
public String dialect() {
return "sqlserver";
}
}
再重複上面的訪問流程,當觸發限流時,會提示如下
總結
自定義spi的實現系列接近尾聲了,其實這個小demo並沒有多少原創的東西,大都從dubbo、shenyu、mybatis、spring、sentinel原始碼中抽出一些比較好玩的東西,東拼西湊出來的一個demo。
平時我們大部分時間還是在做業務開發,可能有些人會覺得天天crud,感覺沒啥成長空間,但只要稍微留意一下我們平時經常使用的框架,說不定就會發現一些不一樣的風景