流量治理神器-Sentinel 究竟是怎麼做到讓業務方接入簡單?

架構擺渡人發表於2022-05-15

大家好,我是架構擺渡人,這是流量治理系列的第10篇原創文章,如果有收穫,還請分享給更多的朋友。

做業務開發,需要考慮業務的擴充套件性。做基礎框架開發,需要考慮如何讓業務方接入,使用簡單,儘量不要耦合在業務程式碼中。

Sentinel裡面是如何做到讓業務方接入簡單,使用方便的呢?這篇文章就來剖析下Sentinel的那些適配是如何實現的。

基本使用

基本使用可以直接用SphU類對資源進行保護,使用方式如下:

public static void main(String[] args) {
    // 配置規則.
    initFlowRules();
    while (true) {
        // 1.5.0 版本開始可以直接利用 try-with-resources 特性
        try (Entry entry = SphU.entry("HelloWorld")) {
            // 被保護的邏輯
            System.out.println("hello world");
	   } catch (BlockException ex) {
            // 處理被流控的邏輯
	    System.out.println("blocked!");
	   }
    }
}

這種方式其實就違背了前面我們說的耦合問題,跟業務程式碼耦合在了一起,擴充套件性不好。

註解使用

可以在基本使用的基礎上優化下,不在業務程式碼中出現SphU相關的程式碼,比如說定義一個註解在來實現這個功能。

使用方式如下:

@SentinelResource("HelloWorld")
public void helloWorld() {
    // 資源中的邏輯
    System.out.println("hello world");
}

通過註解和切面我們就可以將邏輯收攏,不會在散落在各個業務程式碼中,就算有一天你的限流方式改成了其他框架,註解都不用變,直接將切面裡面的邏輯更新即可。

適配Dubbo

當我們需要對Dubbo的介面進行限流時,使用原生的程式碼方式和註解方式都可以,但是這樣就需要我們在每個呼叫的地方進行改造,那麼能不能做成自動適配的方式,連註解都不用加呢?

在Sentinel中有一個sentinel-apache-dubbo-adapter的模組就是專門用於適配dubbo的。原理就是通過Dubbo的Filter機制來實現通用的適配邏輯。

有consumer Filter和provider Filter, 業務方只需要依賴這個包,就自動適配dubbo了,然後通過Sentinel的控制檯進行配置,就可以達到限流和熔斷的效果了。

適配Feign

Dubbo都適配了,怎麼能少的了Feign呢,其實原理都是一樣,也是通過Feign的Filter機制來適配。不過Feign的適配整合放在了Spring Cloud Alibaba中。

通過定義SentinelInvocationHandler,在invoke方法中適配Sentinel的邏輯。詳細程式碼在com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler中。

適配Zuul

對於Zuul的適配同樣有一個單獨的模組sentinel-zuul-adapter。原理呢還是一樣,Zuul也有Filter, 既然是限流在Zuul中肯定是用pre filter。

實現類是com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter,貼一小段程式碼給大家看下:

public Object run() throws ZuulException {
    RequestContext ctx = RequestContext.getCurrentContext();
    String origin = this.parseOrigin(ctx.getRequest());
    String routeId = (String)ctx.get("proxy");
    Deque<AsyncEntry> asyncEntries = new ArrayDeque();
    String fallBackRoute = routeId;
    try {
        if (StringUtil.isNotBlank(routeId)) {
            ContextUtil.enter("sentinel_gateway_context$$route$$" + routeId, origin);
            this.doSentinelEntry(routeId, 0, ctx, asyncEntries);
        }
        Set<String> matchingApis = this.pickMatchingApiDefinitions(ctx);
        if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) {
            ContextUtil.enter("zuul_default_context", origin);
        }
        Iterator var14 = matchingApis.iterator();
        while(var14.hasNext()) {
            String apiName = (String)var14.next();
            this.doSentinelEntry(apiName, 1, ctx, asyncEntries);
        }
    } catch (BlockException var12) {
        ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(fallBackRoute);
        BlockResponse blockResponse = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, var12);
        ctx.setRouteHost((URL)null);
        ctx.set("serviceId", (Object)null);
        ctx.setResponseBody(blockResponse.toString());
        ctx.setResponseStatusCode(blockResponse.getCode());
        ctx.getResponse().setContentType("application/json; charset=utf-8");
    } finally {
        if (!asyncEntries.isEmpty()) {
            ctx.put("_sentinel_entries", asyncEntries);
        }
    }
    return null;
}

總結

本文只是為了讓大家瞭解,在開發一個底層框架的時候,需要考慮的問題。這個問題就是使用起來越簡單越好,這才是好的框架該有的樣子。

當然,Sentinel還適配了其他很多的框架,比如httpclient, Spring Cloud Gateway啊等,底層思想都是相同的,都是利用擴充套件機制進行統一處理。

相關文章