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