Preface
Before we about 161aeb927ec2df about how to implement an SPI with interceptor function. At that time, the core idea we realized was to use the responsibility chain + dynamic agency. Today we will talk about how to integrate sentinel to achieve current limiting through dynamic agents.
Pre-knowledge
Introduction to alibaba sentinel
Sentinel is a flow control component oriented to a distributed service architecture. It mainly uses flow as the entry point to help developers ensure the stability of microservices from multiple dimensions such as current limiting, flow shaping, fuse degradation, system load protection, and hotspot protection.
sentinel workflow
sentinel keywords
Resources + rules
Sentinel implements template routines
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
Realization idea
Overall realization idea: dynamic agent + sentinel realization routine template
Core code
Dynamic proxy part
1. Define the dynamic proxy interface
public interface CircuitBreakerProxy {
Object getProxy(Object target);
Object getProxy(Object target,@Nullable ClassLoader classLoader);
}
2. Define the specific dynamic implementation of JDK or cglib
Take 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);
//用InvocationTargetException包裹是java.lang.reflect.UndeclaredThrowableException问题
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
3. Specific call of dynamic agent
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: above dynamic proxy implementation idea is to refer to the spring aop dynamic proxy implementation
The specific reference class is 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
Sample demo
Sample preparation
1. Define the interface implementation class and add a fuse annotation
@CircuitBreakerActivate(spiKey = "sqlserver",fallbackFactory = SqlServerDialectFallBackFactory.class)
public class SqlServerDialect implements SqlDialect {
@Override
public String dialect() {
return "sqlserver";
}
}
ps: @CircuitBreakerActivate This is a custom circuit breaker annotation. After using the @FeignClient annotation of springcloud openfeign, there will probably be a sense of familiarity. All of them are to configure fallbackFactory or fallbackBack on the annotations.
2. Define the 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: is also very familiar to see if this is also familiar. Isn't this the fuse factory of springcloud hystrix?
3. Configure sentinel dashbord address in the project
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
filter:
enabled: false
Example verification
1. The browser visits http://localhost:8082/test/ciruitbreak
At this time, the access is normal, take a look at sentinel-dashbord
2. Configure current limiting rules
3. Quick access again
It can be seen from the figure that the fuse has been triggered
In this sample project, if fallback or fallbackFactory is not configured, it will also have a default fallback when the current limit 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 again, when the current limit is triggered, the following will be prompted
Summarize
The implementation series of custom spi is coming to an end. In fact, this small demo doesn't have much original stuff. Most of them extract some fun stuff from dubbo, shenyu, mybatis, spring, sentinel source code, and piece together a demo.
Usually we are still doing business development most of the time. Some people may think that crud every day and feel that there is no room for growth, but as long as we pay a little attention to the frameworks we usually use, we may find some different landscapes.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。