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

2021-12-13 18:07:42 浏览数 (1)

01前言

之前我们聊了一下聊聊如何实现一个带有拦截器功能的SPI。当时我们实现的核心思路是利用了责任链 动态代理。今天我们再聊下通过动态代理如何去整合sentinel实现熔断限流

02前置知识

01alibaba sentinel简介

Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

02sentinel工作流程

03sentinel关键词

资源 规则

04sentinel实现模板套路

代码语言:javascript复制
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();
  }
}

05sentinel wiki

https://github.com/alibaba/Sentinel/wiki/主页

03实现思路

整体实现思路:动态代理 sentinel实现套路模板

核心代码

动态代理部分

01定义动态代理接口

代码语言:javascript复制
public interface CircuitBreakerProxy {

    Object getProxy(Object target);

    Object getProxy(Object target,@Nullable ClassLoader classLoader);
}

02定义JDK或者cglib具体动态实现

以jdk动态代理为例子

代码语言:javascript复制
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();
        }

    }
}

03动态代理具体调用

代码语言:javascript复制
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动态代理实现

具体参考类如下

代码语言:javascript复制
org.springframework.aop.framework.AopProxy
org.springframework.aop.framework.DefaultAopProxyFactory

sentinel实现部分

代码语言:javascript复制
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原生的实现套路逻辑

示例演示

示例准备

01定义接口实现类并加上熔断注解

代码语言:javascript复制
@CircuitBreakerActivate(spiKey = "sqlserver",fallbackFactory = SqlServerDialectFallBackFactory.class)
public class SqlServerDialect implements SqlDialect {
    @Override
    public String dialect() {
        return "sqlserver";
    }

}

ps: @CircuitBreakerActivate这个是自定义熔断注解,用过springcloud openfeign的@FeignClient注解大概就会有种熟悉感,都是在注解上配置fallbackFactory或者fallbackBack

02定义接口熔断工厂

代码语言:javascript复制
@Slf4j
@Component
public class SqlServerDialectFallBackFactory implements FallbackFactory<SqlDialect> {

    @Override
    public SqlDialect create(Throwable ex) {
        return () -> {
            log.error("{}",ex);
            return "SqlServerDialect FallBackFactory";
        };
    }
}

ps: 看这个是不是也很熟悉,这不就是springcloud hystrix的熔断工厂吗

03项目中配置sentinel dashbord地址

代码语言:javascript复制
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      filter:
        enabled: false

示例验证

1、浏览器访问http://localhost:8082/test/ciruitbreak

此时访问正常,看下sentinel-dashbord

2、配置限流规则

2、再次快速访问

由图可以看出已经触发熔断

本示例项目,如果不配置fallback或者fallbackFactory,当触发限流时,它也会有个默认fallback

把示例注解的fallbackFactory去掉,如下

代码语言:javascript复制
@CircuitBreakerActivate(spiKey = "sqlserver")
public class SqlServerDialect implements SqlDialect {
    @Override
    public String dialect() {
        return "sqlserver";
    }

}

再重复上面的访问流程,当触发限流时,会提示如下

04总结

自定义spi的实现系列接近尾声了,其实这个小demo并没有多少原创的东西,大都从dubbo、shenyu、mybatis、spring、sentinel源码中抽出一些比较好玩的东西,东拼西凑出来的一个demo。

平时我们大部分时间还是在做业务开发,可能有些人会觉得天天crud,感觉没啥成长空间,但只要稍微留意一下我们平时经常使用的框架,说不定就会发现一些不一样的风景

05demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-circuitbreaker

0 人点赞