聊聊自定义SPI如何与sentinel整合实现熔断限流
前言
之前我们聊了一下聊聊如何实现一个带有拦截器功能的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.AopProxy org.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 @Component public 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
示例验证
此时访问正常,看下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链接

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
沙盒化容器:是容器还是虚拟机
随着 IT 技术的发展,AI、区块链和大数据等技术提升了对应用毫秒级扩展的需求,开发人员也面临着的功能快速推出的压力。混合云是新常态,数字化转型是保持竞争力的必要条件,虚拟化成为这些挑战的基本技术。 在虚拟化的世界,有两个词耳熟能详:虚拟机和容器。前者是对硬件的虚拟化,后者则更像是操作系统的虚拟化。两者都提供了沙箱的能力:虚拟机通过硬件级抽象提供,而容器则使用公共内核提供进程级的隔离。有很多人将容器看成是“轻量化的虚拟机”,通常情况下我们认为容器是安全的,那到底是不是跟我们想象的一样? 容器:轻量化的虚拟机? 容器是打包、共享和部署应用的现代化方式,帮助企业实现快速、标准、灵活地完成服务交互。容器化是建立在 Linux 的命名空间(namespace)和控制组(cgroup) 的设计之上。 命名空间创建一个几乎隔离的用户空间,并为应用提供专用的系统资源,如文件系统、网络堆栈、进程ID和用户ID。随着用户命名空间的引入,内核版本 3.8 提供了对容器功能的支持:Mount(mnt)、进程 ID(pid)、Network(net)、进程间通信(ipc)、UTS、用户 ID(user)6 个...
- 下一篇
dart系列之:HTML的专属领域,除了javascript之外,dart也可以
简介 虽然dart可以同时用作客户端和服务器端,但是基本上dart还是用做flutter开发的基本语言而使用的。除了andorid和ios之外,web就是最常见和通用的平台了,dart也提供了对HTML的原生支持,这个支持就是dart:html包。 dart:html提供了对DOM对象的各种有用的操作和对HTML5 API的支持。这样我们可以直接使用dart来操作HTML。 除了DOM之外,dart:html还可以对css进行操作,使用dart:html也非常简单: import 'dart:html'; DOM操作 对于DOM操作来说,首先是需要找到这个元素。 dart提供了querySelector() 和 querySelectorAll()方法,可以根据ID, class, tag, name或者这些元素的集合来进行查找。 同样都是query方法,两者的不同在于,querySelector只返回找到的第一个元素,而querySelectorAll返回找到的所有元素。 所以querySelector返回的是一个Element,而querySelectorAll返回的是一个集合L...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8编译安装MySQL8.0.19
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)