How to integrate custom SPI with sentinel to realize fuse current limiting

Posted by rickphp on Mon, 13 Dec 2021 11:56:16 +0100

01 Preface

We talked before Talk about how to implement an SPI with interceptor function . At that time, the core idea of our implementation was to use responsibility chain + dynamic agent. Today, let's talk about how to integrate sentinel to realize fuse current limiting through dynamic agent

02 pre knowledge

01introduction to Alibaba Sentinel

Sentinel is a flow control component for distributed service architecture. It mainly takes flow as the starting point to help developers ensure the stability of micro services from multiple dimensions such as current limiting, flow shaping, fuse degradation, system load protection and hotspot protection.

02sentinel workflow

03sentinel keyword

Resource + rule

04sentinel implementation template routine

Entry entry = null;
// Make sure finally is executed
try {
  // Any string with business semantics can be used for the resource name. Note that the number cannot be too large (more than 1K), and more than thousands. Please pass it in as a parameter instead of directly as the resource name
  // EntryType represents the flow type (inbound/outbound), where the system rule is only effective for embedded points of type IN
  entry = SphU.entry("Custom resource name");
  // Protected business logic
  // do something...
} catch (BlockException ex) {
  // Resource access is blocked, restricted or degraded
  // Carry out corresponding processing operations
} catch (Exception ex) {
  // If you need to configure degradation rules, you need to record business exceptions in this way
  Tracer.traceEntry(ex, entry);
} finally {
  // Ensure exit, and ensure that each entry is paired with exit
  if (entry != null) {
    entry.exit();
  }
}

05sentinel wiki

https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5

03 implementation ideas

Overall implementation idea: dynamic agent + sentinel implementation routine template

Core code

Dynamic agent part

01 define dynamic proxy interface

public interface CircuitBreakerProxy {

    Object getProxy(Object target);

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

02 define the specific dynamic implementation of JDK or cglib

Taking jdk dynamic agent as an example

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);
            //The package with InvocationTargetException is Java lang.reflect. Undeclaredtowableexception problem
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }

    }
}

03 dynamic proxy specific call

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: the above dynamic proxy implementation idea refers to the spring aop dynamic proxy implementation

Specific reference classes are as follows

org.springframework.aop.framework.AopProxy
org.springframework.aop.framework.DefaultAopProxyFactory

sentinel implementation part

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: if you are a careful friend, you will find that this logic is sentinel's native implementation routine logic

Example demonstration

Sample preparation

01 define the interface implementation class and annotate it

@CircuitBreakerActivate(spiKey = "sqlserver",fallbackFactory = SqlServerDialectFallBackFactory.class)
public class SqlServerDialect implements SqlDialect {
    @Override
    public String dialect() {
        return "sqlserver";
    }

}

ps: @ CircuitBreakerActivate is a custom annotation. You will probably feel familiar with the @ FeignClient annotation of springcloud openfeign. You configure fallbackFactory or fallbackon the annotation

02 define interface fuse factory

@Slf4j
@Component
public class SqlServerDialectFallBackFactory implements FallbackFactory<SqlDialect> {

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

ps: see if this is also familiar. Isn't this the fuse factory of springcloud hystrix

03 configure sentinel dashbord er address in project

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      filter:
        enabled: false

Example validation

1. Browser access http://localhost:8082/test/ciruitbreak

At this time, the access is normal. Take a look at sentinel dashborder

2. Configure current limiting rules

2. Quick access again

It can be seen from the figure that the fuse has been triggered

In this example project, if fallback or fallbackFactory is not configured, it will also have a default fallback when current limiting is triggered

Remove the fallbackFactory of the example annotation as follows

@CircuitBreakerActivate(spiKey = "sqlserver")
public class SqlServerDialect implements SqlDialect {
    @Override
    public String dialect() {
        return "sqlserver";
    }

}

Repeat the above access process. When the current limit is triggered, the following message will be prompted

04 summary

The implementation series of custom spi is coming to an end. In fact, there are not many original things in this small demo. Most of them pull out some interesting things from the source codes of dubbo, shenyu, mybatis, spring and sentinel, and put together a demo.

We are still doing business development most of the time. Some people may feel crud every day and there is no room for growth, but as long as we pay a little attention to the framework we often use, we may find some different scenery

05demo link

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