限流-Sentinel使用

上篇文章介绍了sentinel的简单示例和一些架构、基本原理。本文重点看看如何和一些已有系统整合使用。

框架适配

servlet

其实很好理解,做一个filter就可以了。官方已经有对应适配:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-web-servlet</artifactId>
    <version>x.y.z</version>
</dependency>

CommonFilter代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    HttpServletRequest sRequest = (HttpServletRequest)request;
    Entry entry = null;
    try {
        String target = FilterUtil.filterTarget(sRequest);
        UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
        if (urlCleaner != null) {
            target = urlCleaner.clean(target);
        }
        String origin = parseOrigin(sRequest);
        ContextUtil.enter(target, origin);
        entry = SphU.entry(target, EntryType.IN);
        chain.doFilter(request, response);
    } catch (BlockException e) {
        HttpServletResponse sResponse = (HttpServletResponse)response;
        WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
    } catch (IOException e2) {
        Tracer.trace(e2);
        throw e2;
    } catch (ServletException e3) {
        Tracer.trace(e3);
        throw e3;
    } catch (RuntimeException e4) {
        Tracer.trace(e4);
        throw e4;
    } finally {
        if (entry != null) {
            entry.exit();
        }
        ContextUtil.exit();
    }
}

类型一 /hello resourece: hello context:hello origin: ""

类型二 如果是restapi方式如:/user/{id},此时如果不做配置,会根据id出现很多个资源,但其实资源是user。此时可以注册一个url清理器:

WebCallbackManager.setUrlCleaner(new UrlCleaner() {
        @Override
        public String clean(String originUrl) {
            if (originUrl.startsWith("/user/")) {
                return "/user/*";
            }
            return originUrl;
        }
    });

这样,他的资源名就变成了/user/*

类型三 如果我想对某一个调用我的源限流,比如我只限制pc访问,或者只限制某个系统过来的请求,origin就不能是”“,怎么设置能,sentinel提供了解析方式,从header读取资源作为origin。至于读取什么header,由自己定义。

WebCallbackManager.setRequestOriginParser(new RequestOriginParser() {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getHeader(headerName);
        return origin != null ? origin : "";
    }
});

如果限流后我希望跳转到指定页面,也可以配置:

WebServletConfig.setBlockPage(redirectUrl);

spring mvc

和servlet一样,引入相同包,配置过滤器即可:

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        registration.setName("sentinelFilter");
        registration.setOrder(1);

        return registration;
    }
}

doubbo

官方是配置

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-dubbo-adapter</artifactId>
    <version>x.y.z</version>
</dependency>

原理和servlet差不多,也是自定义一个两个filter,一个消费的,一个提供者的。比如:

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();
        // 链路名为resourceName,origin是applicationname
        ContextUtil.enter(resourceName, application);
        // 两个entry,分别是`接口`和`接口:方法`。
        interfaceEntry = SphU.entry(interfaceName, EntryType.IN);
        methodEntry = SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());

        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();
    }
}

还有grpc适配什么的,反正原理都差不多,如果自定义是配置,统一注意几个名词就好:链路,源,资源切入口。

WebCallbackManager设计比较赞,让用户自定义解析方法。

自定义slot

上篇文章有讲,sentinel使用责任链方式,把一个一个插槽连起来。

// spi的方式,让使用方实现SlotChainBuilder。
private static final ServiceLoader<SlotChainBuilder> LOADER = ServiceLoader.load(SlotChainBuilder.class);
private static void resolveSlotChainBuilder() {
    List<SlotChainBuilder> list = new ArrayList<SlotChainBuilder>();
    boolean hasOther = false;
    for (SlotChainBuilder builder : LOADER) {
        if (builder.getClass() != DefaultSlotChainBuilder.class) {
            hasOther = true;
            list.add(builder);
        }
    }
    if (hasOther) {
        builder = list.get(0);
    } else {
        // 没有自定义slotbuilder就使用DefaultSlotChainBuilder。
        builder = new DefaultSlotChainBuilder();
    }
}

从代码可以看到,主要是通过spi方式实现。所以实现接口,并定义services即可:

public class DemoSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultSlotChainBuilder().build();
        // 把子顶一个DemoSlot加入链中
        chain.addLast(new DemoSlot());
        return chain;
    }
}

新建META-INFO/services目录,指定spi实现。

数据源

sentinel已经提供了很多数据源的实现,比如zk,redis,appollo等。自己实现主要做两件事:

  1. 实现AbstractDataSource。主要是获取数据源连接,load配置,监听配置等(注意更新方式)。
  2. 指定配置解析器,比如json解析。
CONTENTS