源码分析 Sentinel 之 Dubbo 适配原理

Alibaba Sentinel 限流与熔断初探(技巧篇) 的示例中我选择了 sentinel-demo-apache-dubbo 作为突破点,故本文就从该项目入手,看看 Sentinel 是如何对 Dubbo 做的适配,让项目使用方无感知,只需要引入对应的依即可。

sentinel-apache-dubbo-adapter 比较简单,展开如下:

上面的代码应该比较简单,在正式进入源码研究之前,我先抛出如下二个问题:

  • 1、限流、熔断相关的功能是在 Dubbo 的客户端实现还是服务端实现?为什么?
  • 2、如何对 Dubbo 进行功能扩展而无需改动业务代码?

Dubbo 提供了 Filter 机制对功能进行无缝扩展,有关 Dubbo Filter 机制,大家可以查阅笔者的源码研究 Dubbo 系列:Dubbo Filter机制概述

接下来我们带着上面的问题1开始本章的研究。

@

目录

  • 1、源码分析 SentinelDubboConsumerFilter
  • 2、源码分析 SentienlDubboProviderFilters
  • 3、Sentienl Dubbo FallBack 机制
  • 4、总结

1、源码分析 SentinelDubboConsumerFilter

@Activate(group = "consumer")   // @1
public class SentinelDubboConsumerFilter implements Filter {

    public SentinelDubboConsumerFilter() {
        RecordLog.info("Sentinel Apache Dubbo consumer filter initialized");
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Entry interfaceEntry = null;
        Entry methodEntry = null;
        try {
            String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix());   // @2
            interfaceEntry = SphU.entry(invoker.getInterface().getName(),
                ResourceTypeConstants.COMMON_RPC, EntryType.OUT);     // @3
            methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);    // @4

            Result result = invoker.invoke(invocation);            // @5
            if (result.hasException()) {                                     // @6
                Throwable e = result.getException();
                // Record common exception.
                Tracer.traceEntry(e, interfaceEntry);
                Tracer.traceEntry(e, methodEntry);
            }
            return result;
        } catch (BlockException e) {
            return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e);  // @7
        } catch (RpcException e) {
            Tracer.traceEntry(e, interfaceEntry);
            Tracer.traceEntry(e, methodEntry);
            throw e;
        } finally {
            if (methodEntry != null) {   // @8
                methodEntry.exit();
            }
            if (interfaceEntry != null) {
                interfaceEntry.exit();
            }
        }
    }
}

代码@1:通过 @Activate 注解定义该 Filter 在客户端生效。

代码@2:在 Sentinel 中一个非常核心的概念就是资源,即要定义限流的目标,当出现什么异常(匹配用户配置的规则)对什么进行熔断操作,Dubbo 服务中的资源通常是 Dubbo 服务,分为服务接口级或方法级,故该方法返回 Dubbo 的资源名,其主要实现特征如下:

  • 如果启用用户定义资源的前缀,默认为 false ,可以通过配置属性:csp.sentinel.dubbo.resource.use.prefix 来定义是否需要启用前缀。如果启用前缀,消费端的默认前缀为 dubbo:consumer:,可以通过配置属性 csp.sentinel.dubbo.resource.consumer.prefix 来自定义消费端的资源前缀。
  • Dubbo 资源的名称表示方法为:interfaceName + ":" + methodName + "(" + "paramTyp1参数列表,多个用 , 隔开" + ")"。

代码@3:调用 Sentinel 核心API SphU.entry 进入 Dubbo InterfaceName。从方法的名称我们也能很容易的理解,就是使用 Sentienl API 进入资源名为 Dubbo 接口提供者类全路径限定名,即认为调用该方法,Sentienl 会收集该资源的调用信息,然后Sentinel 根据运行时收集的信息,再配合限流规则,熔断等规则进行计算是否需要限流或熔断。本节我们不打算深入研究 SphU 的核心方法研究,先初步了解该方法:

  • String name 资源的名称。
  • int resourceType 资源的类型,在 Sentinel 中目前定义了 如下五中资源:
    • ResourceTypeConstants.COMMON

      同样类型。

    • ResourceTypeConstants.COMMON_WEB

      WEB 类资源。

    • ResourceTypeConstants.COMMON_RPC

      RPC 类型。

    • ResourceTypeConstants.COMMON_API_GATEWAY

      接口网关。

    • ResourceTypeConstants.COMMON_DB_SQL

      数据库 SQL 语句。

  • EntryType type

    进入资源的方式,主要分为 EntryType.OUT、EntryType.IN,只有 EntryType.IN 方式才能对资源进行阻塞。

代码@4:调用 Sentinel 核心API SphU.entry 进入 Dubbo method 级别。

代码@5:调用 Dubbo 服务提供者方法。

代码@6:如果出现调用异常,可以通过 Sentinel 的 Tracer.traceEntry 跟踪本次调用资源进入的情况,详细 API 将在该系列的后续文章中详细介绍。

代码@7:如果是由于触发了限流、熔断等操作,抛出了阻塞异常,可通过 注册 ConsumerFallback 来实现消费者快速失败,将在下文详细介绍。

代码@8: SphU.entry 与 资源的 exit 方法需要成对出现,否则会出现统计错误。

2、源码分析 SentienlDubboProviderFilters

@Activate(group = "provider")
public class SentinelDubboProviderFilter implements Filter {
    public SentinelDubboProviderFilter() {
        RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
    }
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // Get origin caller.
        String application = DubboUtils.getApplication(invocation, "");
        Entry interfaceEntry = null;
        Entry methodEntry = null;
        try {
            String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix());   // @1
            String interfaceName = invoker.getInterface().getName();
            // Only need to create entrance context at provider side, as context will take effect
            // at entrance of invocation chain only (for inbound traffic).
            ContextUtil.enter(resourceName, application);
            interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);  // @2
            methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC,
                EntryType.IN, invocation.getArguments());
            Result result = invoker.invoke(invocation);
            if (result.hasException()) {
                Throwable e = result.getException();
                // Record common exception.
                Tracer.traceEntry(e, interfaceEntry);
                Tracer.traceEntry(e, methodEntry);
            }
            return result;
        } catch (BlockException e) {
            return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e);   // @3
        } catch (RpcException e) {
            Tracer.traceEntry(e, interfaceEntry);
            Tracer.traceEntry(e, methodEntry);
            throw e;
        } finally {
            if (methodEntry != null) {
                methodEntry.exit(1, invocation.getArguments());
            }
            if (interfaceEntry != null) {
                interfaceEntry.exit();
            }
            ContextUtil.exit();
        }
    }
}

Dubbo 服务提供者与消费端的适配套路差不多,这里就重点阐述一下其不同点。

代码@1:如果启用前缀,默认服务提供者的资源会加上前缀:dubbo:provider:,可以通过在配置文件中配置属性 csp.sentinel.dubbo.resource.provider.prefix 改变其默认值。

代码@2:服务端调用 SphU.entry 时其进入类型为 EntryType.IN。

代码@3:同样可以在 抛出阻塞异常(BlockException) 时指定快速失败回调处理逻辑。

3、Sentienl Dubbo FallBack 机制

Sentinel Dubbo FallBack 机制比较简单,就是提供一个全局的 FallBack 回调,可以分别为服务提供端,服务消费端指定。只需实现 DubboFallback 接口,其声明如下:

然后需要调用 DubboFallbackRegistry 的 setConsumerFallback 和 setProviderFallback 方法分别注册消费端,服务端相关的监听器。通常只需要在启动应用的时候,将其进行注册即可。

4、总结

本文只是以 Sentienl 对 Dubbo 的适配实现来了解 Sentinel 核心相关的 API,其核心实现就是利用 Dubbo 的 Filter 机制进行无缝的过滤拦截。但本文只是提到 Sentinel 如下核心方法:

  • SphU.entry
  • Entry.exit
  • Tracer.traceEntry

上述这些方法,将在后面的文章中进行深入探究,即从下一篇文章开始,我们将真正进入 Sentinel 的世界中,让我们一探究竟限流、熔断通常是如何实现的。

本文就介绍到这里了,点赞是一种美德,您的点赞是我持续分享的最大动力,谢谢。

推荐阅读:源码分析 Alibaba Sentinel 专栏。

1、Alibaba Sentinel 限流与熔断初探(技巧篇)


作者信息:丁威,《RocketMQ技术内幕》作者,目前担任中通科技技术平台部资深架构师,维护 中间件兴趣圈公众号,目前主要发表了源码阅读java集合、JUC(java并发包)、Netty、ElasticJob、Mycat、Dubbo、RocketMQ、mybaits等系列源码。点击链接:加入笔者的知识星球,一起探讨高并发、分布式服务架构,分享阅读源码心得。

原文地址:https://www.cnblogs.com/dingwpmz/p/12687767.html

时间: 2024-10-06 11:46:31

源码分析 Sentinel 之 Dubbo 适配原理的相关文章

5.Sentinel源码分析—Sentinel如何实现自适应限流?

Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Sentinel源码分析- QPS流量控制是如何实现的? 4.Sentinel源码分析- Sentinel是如何做到降级的? 这篇文章主要学习一下Sentinel如何实现自适应限流的. 为什么要做自适应限流,官方给了两个理由: 保证系统不被拖垮 在系统稳定的前提下,保持系统的吞吐量 我再贴一下官方的原理: 能

4.Sentinel源码分析— Sentinel是如何做到降级的?

各位中秋节快乐啊,我觉得在这个月圆之夜有必要写一篇源码解析,以表示我内心的高兴~ Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Sentinel源码分析- QPS流量控制是如何实现的? 在我的第二篇文章里面2. Sentinel源码分析-Sentinel是如何进行流量统计的?里面介绍了整个Sentinel的主流程是怎样的.所以降级的大致流程可以概述为:

6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?

Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Sentinel源码分析- QPS流量控制是如何实现的? 4.Sentinel源码分析- Sentinel是如何做到降级的? 5.Sentinel源码分析-Sentinel如何实现自适应限流? 有时候我们做限流的时候并不想直接写死在代码里面,然后每次要改规则,或者增加规则的时候只能去重启应用来解决.而是希望能

7.Sentinel源码分析—Sentinel是怎么和控制台通信的?

这里会介绍: Sentinel会使用多线程的方式实现一个类Reactor的IO模型 Sentinel会使用心跳检测来观察控制台是否正常 Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Sentinel源码分析- QPS流量控制是如何实现的? 4.Sentinel源码分析- Sentinel是如何做到降级的? 5.Sentinel源码分析-Sentinel如

Guava 源码分析之Cache的实现原理

Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的. 缓存 本次主要讨论缓存.缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能. 缓存之所以可以提高性能是因为它的读取效率很高,就像是 CPU 的 L1.L2.L3 缓存一样,级别越高相应的读取速度也会越快. 但也

源码分析 脱壳神器ZjDroid工作原理

0. 神器ZjDroid Xposed框架的另外一个功能就是实现应用的简单脱壳,其实说是Xposed的作用其实也不是,主要是模块编写的好就可以了,主要是利用Xposed的牛逼Hook技术实现的,下面就先来介绍一下这个脱壳模块工具ZjDroid的原理,因为他是开源的,所以咋们直接分析源码即可,源码的下载地址:https://github.com/halfkiss/ZjDroid 不过可惜的时候他只公开了Java层的代码,而native层的代码并没有公开,但是分析源码之后会发现最重要的功能就在nat

Mybatis源码分析之Cache二级缓存原理 (五)

一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(ServiceProvider Interface) ,所有的MyBatis内部的Cache缓存,都应该实现这一接口 Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现. 看下cache的实现类,如下图: 1.FIFOCache:先进

Tomcat7.0源码分析——启动与停止服务原理

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

同步锁源码分析(一)AbstractQueuedSynchronizer原理

文章转载自 AbstractQueuedSynchronizer的介绍和原理分析 建议去看一下原文的评论,会有不少收获. 简介 AbstractQueuedSynchronizer 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态.然而多线程环