1. 前言
目前在企业中使用最多的莫不过是http接口和dubbo接口的限流,本篇文章将会向大家完整介绍一下http接口限流和dubbo接口限流的原理,以及会对他们的源码进行一个详细的分析,sentinel是如何对此进行了高效的的且不侵入代码的完美适配的。
2. sentinel-adapter
如图我们可以看到,sentinel非常良心地向我们提供了大量的适配器,那么他们的作用是什么呢?
2.1 适配器的作用
看过前面文章的小伙伴肯定都知道,我们调用sentinel来实现限流的关键入口其实是“SphU.entry()”,这个大家应该都了解,限流的核心其实就是调用SphU的核心方法,来判定本次请求是否能够通过这段链路,如下图所示:
我们在有的时候也会使用注解的方式进行指定接口的限流,就像下面这种形式
@SentinelResource(value = "资源名", blockHandler = "限流回调方法名")
但是这些方式是对代码有侵入性的,因此sentinel提供了适配器,适配器能够让用户方在无感知的情况下,只要引入对应的jar包,就能对http或者dubbo接口进行限流,是不是很香。
3. http接口限流
3.1 http限流使用过程
- 首先使用http限流,项目需要引入sentinel的sentinel-web-servlet的jar包。同时,因为我们演示时接入了dashboard,所以我还需要引入sentinel-transport-simple-http的jar包,依赖如下
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
复制代码
当然,如果是spring boot的项目可以直接引入spring-cloud-starter-alibaba-sentinel。版本可自选
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.2.0.RELEASE</version>
</dependency>
复制代码
- 编写接口,启动dashboard设置规则,注意资源名必须为http接口的路径名(原因会在后续源码分析时说明),点击新增后,这个规则就会生效了,是不是相当轻松。(dashboard的接入在前面文章已经有所介绍,可以自行查看)
3.2 http限流、降级逻辑注册
接下来我们就是编写限流回调逻辑了,适配器为我们提供了一套默认的限流回调,如下图所示。
当然我们也可以编写一套自己的逻辑,建议在spring boot 启动的main方法里写上(其实哪里写都无所谓,这是个静态的回调方法,只要启动后能加载到就可以),代码如下:
WebCallbackManager.setUrlBlockHandler((request, response, ex) ->{
System.out.println("限流处理的业务逻辑");
});
复制代码
3.3 Http限流源码解析
3.3.1 项目结构
3.3.2 核心源码解析
其实通过上方的项目结构,我们一下子就能发现,其实http适配器主要的核心思路,就是进行了一个过滤器的操作来进行请求的拦截,过滤。
对于过滤器,此处不过多介绍,感兴趣的可以自行查阅资料。他的作用就是所有的http请求都会经过过滤器,因此我们可以在过滤器一端进行限流操作。
过滤器会过滤到http请求,接着就是从请求中读出请求过来的路径或者方法的后缀,作为资源名传入“SphU.entry()”,进行对该资源的一个流量监控等,因此这就是为什么前面在创建规则需要输入正确的http接口路径名的原因,因为一旦如果资源名不匹配,就会造成因为读取不到对应规则,而造成限流失效的问题(此处sentinel核心限流源码部分省略,会在后续进行完整讲解)。关键源码如下:进行了http请求的拦截,并获取路径,设定调用链的入口。开启限流判定等。
4. dubbo接口限流
4.1 dubbo限流使用过程
- 使用dubbo限流前,同样道理项目需要引入sentinel的sentinel-apache-dubbo-adapter(dubbo高版本适配器,低版本的引用另一个)的jar包。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>${sentinel.version}</version>
</dependency>
复制代码
同样的如果是spring boot的项目可以直接引入spring-cloud-starter-alibaba-sentinel。版本可自选
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.2.0.RELEASE</version>
</dependency>
复制代码
- 和http一样,启动dashboard设置规则,这里的资源名,如果是对某个dubbo的服务接口限流,资源名必须设置为接口的完全限定名,如果是接口的某个方法,则设置为接口全限定名:方法签名,例如: buguniao.UserService:getUserById(java.lang.Long)点击新增后,这个规则就会生效了,这里就不放截图了,除了资源名需要注意外,其余和http基本相同,轻松完爆。
4.2 dubbo限流、降级逻辑注册
接下来我们就是编写限流回调逻辑了,dubbo适配器没有默认的限流回调,如下图所示。所以我们需要编写一套自己的逻辑,同样需要在启动的时候能加载到就可以。
- 有个问题,前面没提,sentinel只能注册全局的回调的逻辑,因此需要在回调方法里区分 限流逻辑、降级逻辑
- 由于sentinel dubbo 限流熔断是基于dubbo filter实现的,因此在最终的返回结果需要返回一个 RpcResult,请参考以下模板,修改限流逻辑部分、降级逻辑部分
代码如下:
DubboFallbackRegistry.setConsumerFallback(new DubboFallback() {
@Override
public Result handle(Invoker<?> invoker,Invocation invocation,BlockException ex) {
RpcResult rs = new RpcResult();
String r = null;
try{
if(ex instanceof FlowException){
r="限流逻辑"; //需要自定义,最终返回业务返回值
}else if(ex instanceof DegradeException){
r="降级逻辑"; //需要自定义,最终返回业务返回值
}
}catch (Exception e){
r="其他错误";
rs.setException(e); //业务异常注入,以便在consumer方抛出异常
}
rs.setValue(r); //设置业务返回值
return rs;
}
});
复制代码
4.3 dubbo限流源码解析
4.3.1 项目结构
4.3.2 核心源码解析
dubbo接口的限流实现,其实是基于dubbo filter实现的,dubbo filter如何配置的我们在此处不过多介绍,我们只聊限流方面的。
首先,该适配器会在dubbo消费者端进行一层拦截,主要是获取url中消费者的应用名,并添加到dubbo的上下文中,这样provider端才能对来源应用进行一个限流。代码如下:
@Activate(group = CONSUMER)
public class DubboAppContextFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY);
if (application != null) {
RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application);
}
return invoker.invoke(invocation);
}
复制代码
consummer端有一层EntryType类型设置为OUT的限流判定,此处暂时省略介绍,会在核心源码解析时具体介绍,部分源码如下:
try {
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT,
invocation.getArguments());
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Tracer.traceEntry(result.getException(), interfaceEntry);
Tracer.traceEntry(result.getException(), methodEntry);
}
return result;
} catch{
//...
}
复制代码
provider端的过滤器的主要作用便是进行EntryType类型设置为IN的限流判定,会进行一个有效的流量判定,确定是否需要限流,前面讲到的dubbo的消费者应用名的作用是为了对指定来源应用进行限流使用的,dashboard的界面如下:
源码如下:
@Activate(group = "provider")
public class SentinelDubboProviderFilter extends AbstractDubboFilter implements Filter {
public SentinelDubboProviderFilter() {
RecordLog.info("Sentinel 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 = getResourceName(invoker, invocation);
//获取接口的指定方法名
String interfaceName = invoker.getInterface().getName();
//一个调用链的入口,参数为资源名和来源应用名
ContextUtil.enter(resourceName, application);
//接口限流判定开启
interfaceEntry = SphU.entry(interfaceName, EntryType.IN);
//方法限流判定开启
methodEntry = SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
//返回的RpcResult
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Tracer.trace(result.getException());
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e);
} catch (RpcException e) {
Tracer.trace(e);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}
复制代码
注:由于大部分业务场景下,服务提供端是需要限流的,则消费端和一般不会接入sentinel,但是这会是一个小bug,如果dubbo消费端没有接入sentinel的dubbo适配器,则无法拦截到dubboName丢进上下文中,因此,不管dashboard如何设置限流来源均会失效,解决方案是进行dubboName的透传,使用
RpcContext.getContext().setAttachment()方法在消费者端处往协请求头里加一个参数。
5.总结
http和dubbo接口的限流,说白了就是拦截到请求,开始并进行限流判定,判断是否继续执行,因此理解起来不会太难,后续文章会详细介绍sentinel是怎么进行限流的监控和判定的!
近期评论