前言

之前咱们聊了一下聊聊如何实现一个带有拦截器性能的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.AopProxyorg.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@Componentpublic 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