乐趣区

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

前言

之前咱们聊了一下聊聊如何实现一个带有拦截器性能的 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,感觉没啥成长空间,但只有略微注意一下咱们平时常常应用的框架,说不定就会发现一些不一样的风光

demo 链接

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

退出移动版