聊聊自定义SPI如何与sentinel整合实现熔断限流

前言 之前我们聊了一下聊聊如何实现一个带有拦截器功能的SPI。当时我们实现的核心思路是利用了责任链+动态代理。今天我们再聊下通过动态代理如何去整合sentinel实现熔断限流
前置知识 alibaba sentinel简介
Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
sentinel工作流程
聊聊自定义SPI如何与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具体动态实现
【聊聊自定义SPI如何与sentinel整合实现熔断限流】以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 {@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...
聊聊自定义SPI如何与sentinel整合实现熔断限流
文章图片

此时访问正常,看下sentinel-dashbord
聊聊自定义SPI如何与sentinel整合实现熔断限流
文章图片

2、配置限流规则
聊聊自定义SPI如何与sentinel整合实现熔断限流
文章图片

3、再次快速访问
聊聊自定义SPI如何与sentinel整合实现熔断限流
文章图片

由图可以看出已经触发熔断
本示例项目,如果不配置fallback或者fallbackFactory,当触发限流时,它也会有个默认fallback
把示例注解的fallbackFactory去掉,如下
@CircuitBreakerActivate(spiKey = "sqlserver") public class SqlServerDialect implements SqlDialect { @Override public String dialect() { return "sqlserver"; }}

再重复上面的访问流程,当触发限流时,会提示如下
聊聊自定义SPI如何与sentinel整合实现熔断限流
文章图片

总结 自定义spi的实现系列接近尾声了,其实这个小demo并没有多少原创的东西,大都从dubbo、shenyu、mybatis、spring、sentinel源码中抽出一些比较好玩的东西,东拼西凑出来的一个demo。
平时我们大部分时间还是在做业务开发,可能有些人会觉得天天crud,感觉没啥成长空间,但只要稍微留意一下我们平时经常使用的框架,说不定就会发现一些不一样的风景
demo链接 https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-circuitbreaker

    推荐阅读