preface

Earlier we talked about how to implement an SPI with an interceptor. At that time, the core idea of our implementation was to use the chain of responsibility + dynamic proxy. Today we are going to talk about how to integrate Sentinel with dynamic proxy to achieve fusing limit

Front knowledge

Alibaba sentinel profile

Sentinel is a flow control component oriented to distributed service architecture. It mainly takes traffic as the entry point, and helps developers guarantee the stability of micro-services from multiple dimensions such as traffic limiting, traffic shaping, fuse downgrading, system load protection, and hotspot protection.

Sentinel workflow

Sentinel keywords

Resources + Rules

Sentinel implements template routines

Entry entry = null;
// Ensure that finally is executed
try {
  The resource name can be any string with business semantics. The number should not be too large (more than 1K). If the number exceeds several thousand, please pass it in as a parameter rather than as the resource name directly
  // EntryType indicates the traffic type (inbound/outbound). System rules only apply to buried points of the IN type
  entry = SphU.entry("Custom resource name");
  // Protected business logic
  // do something...
} catch (BlockException ex) {
  // Resource access blocked, traffic restricted or degraded
  // Perform the corresponding processing operation
} catch (Exception ex) {
  // To configure degradation rules, record service exceptions in this way
  Tracer.traceEntry(ex, entry);
} finally {
  // Be sure to ensure exit. Be sure to pair each entry with exit
  if(entry ! =null) { entry.exit(); }}Copy the code

sentinel wiki

Github.com/alibaba/Sen…

Implementation approach

Overall implementation idea: Dynamic proxy + Sentinel to implement routine template

The core code

Dynamic proxy part

Define the dynamic proxy interface

public interface CircuitBreakerProxy {

    Object getProxy(Object target);

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

Copy the code

2. Define the JDK or cglib dynamic implementation

Take the JDK dynamic proxy 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);
            / / with InvocationTargetException is Java. Lang. Reflect. UndeclaredThrowableException problem
        } catch (InvocationTargetException e) {
            throwe.getTargetException(); }}}Copy the code

Dynamic proxy specific invocation

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 newCircuitBreakerCglibProxy().getProxy(target); }}Copy the code

Ps: The above dynamic proxy implementation idea is a reference to Spring AOP dynamic proxy implementation

The specific reference classes are as follows

org.springframework.aop.framework.AopProxy
org.springframework.aop.framework.DefaultAopProxyFactory
Copy the code

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();
        }

        returnresult; }}Copy the code

Ps: If you are careful, you will find that this logic is the original implementation logic of Sentinel

Example demonstrates

Sample preparation

Define the interface implementation class and add circuit breakers

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

Ps: @circuitBreakerActivate this is a custom circuit breaker annotation. If you have used springCloud OpenFeign’s @FeignClient annotation, you will probably feel familiar with it

2. 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"; }; }}Copy the code

Ps: If this looks familiar, it is the circuit breaker factory of SpringCloud Hystrix

3. Configure sentinel Dashbord address in the project

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      filter:
        enabled: false
Copy the code

Sample validation

1, the browser to http://localhost:8082/test/ciruitbreak

Now, access is fine. Look at Sentinel-Dashbord

2. Configure traffic limiting rules

3. Quick visit again

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

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

Remove the fallbackFactory from the sample annotations as follows

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

Repeat the preceding access flow. When traffic limiting is triggered, the following message will be displayed

conclusion

The implementation series of custom SPI is coming to an end. In fact, this small demo does not have many original things, mostly from dubbo, Shenyu, Mybatis, Spring, Sentinel source code extracted some interesting things, put together a demo.

We spend most of our time doing business development. Some of us may feel cruD every day and have little room for growth. However, as long as we pay a little attention to the framework we often use, we may find some different scenery

The demo link

Github.com/lyb-geek/sp…