給Dubbo添磚加瓦:自定義Filter擴展點實現

🌹🌹若是您以爲個人文章對您有幫助的話,記得在GitHub上star一波哈🌹🌹php

🌹🌹GitHub_awesome-it-blog 🌹🌹html


最近在與同事協同開發一款小而美的應用程序性能監控框架(Pepper-Metrics)。java

其中一個需求,須要收集Dubbo在Provider端和Consumer端的接口響應性能數據,以便存儲到DataSource中或提供給Printer使用。git

在此背景下,咱們須要對Provider端和Consumer端的每一次請求和響應進行監控。在Dubbo中,能夠經過擴展 org.apache.dubbo.rpc.Filter 接口實現。github

0 org.apache.dubbo.rpc.Filter介紹

Filter能夠理解爲調用過程攔截,每次方法調用該攔截器都會生效,擴展時須要注意對性能的影響。spring

用戶定義的Filter默認在已有的Filter以後被執行。apache

1 Pepper-Metrics-Dubbo的Filter實現

在咱們的項目中,這個子模塊被命名爲 Pepper-Metrics-Dubbo,此模塊的結構以下:api

Pepper-Metrics-Dubbo
    |-src/main/java
        |-com.pepper.metrics.integration.dubbo
            |-DubboProfilerFilterTemplate
            |-DubboProviderProfilerFilter
            |-DubboConsumerProfilerFilter
    |-src/main/resources
        |-META-INF
            |-dubbo
                |-org.apache.dubbo.rpc.Filter
複製代碼

Pepper-Metrics-Dubbo 中,DubboProfilerFilterTemplate 類實現了 org.apache.dubbo.rpc.Filter 接口。微信

這是一個模板類,定義了 Filter.invoke() 方法的通用實現,因爲具體收集profile時,針對 ProviderConsumer 須要不一樣的收集器,這裏經過其子類 DubboProviderProfilerFilterDubboConsumerProfilerFilter 分別實現。併發

上述的類關係可經過下圖描述:

Pepper-Metrics-Dubbo類圖

DubboProfilerFilterTemplate大體實現以下:

public abstract class DubboProfilerFilterTemplate implements Filter {

    // Provider的收集器
    static final Stats PROFILER_STAT_IN = Profiler.Builder
            .builder()
            .name("app.dubbo.request.in")
            .build();
    // Consumer的收集器
    static final Stats PROFILER_STAT_OUT = Profiler.Builder
            .builder()
            .name("app.dubbo.request.out")
            .build();

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 省略... 一些必要的準備工做

        // 模板方法,before trace...
        beforeInvoke(tags);
        try {
            Result result = invoker.invoke(invocation);
            // 記錄是否調用報錯
            if (result == null || result.hasException()) {
                isError = true;
            }

            specialException = false;

            return result;
        } finally {
            if (specialException) {
                isError = true;
            }
            // 模板方法,after trace...
            afterInvoke(tags, begin, isError);
        }
    }

    abstract void afterInvoke(String[] tags, long begin, boolean isError);

    abstract void beforeInvoke(String[] tags);
}
複製代碼

兩個實現類以下:

// Provider
@Activate(group = {PROVIDER})
public class DubboProviderProfilerFilter extends DubboProfilerFilterTemplate {
    @Override
    void afterInvoke(String[] tags, long begin, boolean isError) {
        // 記錄響應實現
        PROFILER_STAT_IN.observe(System.nanoTime() - begin, TimeUnit.NANOSECONDS, tags);
        // 併發數遞減
        PROFILER_STAT_IN.decConc(tags);
        // 記錄錯誤數
        if (isError) {
            PROFILER_STAT_IN.error(tags);
        }
    }

    @Override
    void beforeInvoke(String[] tags) {
        // 併發數遞增
        PROFILER_STAT_IN.incConc(tags);
    }
}
複製代碼
// Consumer
@Activate(group = {CONSUMER})
public class DubboConsumerProfilerFilter extends DubboProfilerFilterTemplate {
    @Override
    void afterInvoke(String[] tags, long begin, boolean isError) {
        PROFILER_STAT_OUT.observe(System.nanoTime() - begin, TimeUnit.NANOSECONDS, tags);
        PROFILER_STAT_OUT.decConc(tags);
        if (isError) {
            PROFILER_STAT_OUT.error(tags);
        }
    }

    @Override
    void beforeInvoke(String[] tags) {
        PROFILER_STAT_OUT.incConc(tags);
    }
}
複製代碼

寫完實現類後,須要在項目的 resources 目錄下配置Dubbo的擴展文件。

resources 下建立 META-INF/dubbo/org.apache.dubbo.rpc.Filter 文件,內容以下:

dubboProviderProfiler=com.pepper.metrics.integration.dubbo.DubboProviderProfilerFilter
dubboConsumerProfiler=com.pepper.metrics.integration.dubbo.DubboConsumerProfilerFilter
複製代碼

這樣Dubbo就能夠掃描到自定義的擴展點。

2 自定義Filter的使用

接下來須要將自定義的擴展點配置到Dubbo中,告訴Dubbo我要使用這個Filter,分別在 ProviderConsumer 中配置:

首先看一下 Provider

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <dubbo:application name="demo-provider"/>

    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <bean id="demoService" class="com.pepper.metrics.sample.dubbo.spring.provider.DemoServiceImpl"/>

    <!-- 在這裏配置自定義的擴展點 -->
    <dubbo:service filter="default,dubboProviderProfiler" interface="com.pepper.metrics.sample.dubbo.spring.api.DemoService" ref="demoService" />

</beans>
複製代碼

說明:default 表明已有的擴展點,dubboProviderProfiler 是咱們自定義的擴展點,這樣配置表示咱們自定義的擴展點在已有的擴展點以後執行。

一樣,在 Consumer 端配置自定義擴展點:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <dubbo:application name="demo-consumer"/>

    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- 在這裏配置自定義的擴展點 -->
    <dubbo:reference filter="default,dubboConsumerProfiler" id="demoService" check="true" interface="com.pepper.metrics.sample.dubbo.spring.api.DemoService" />

</beans>
複製代碼

3 自定義擴展點的自動激活

從上文得知,咱們自定義的擴展點必需要修改配置才能生效,這樣一來,是有代碼侵入的。那麼,能不能引入 pepper-metrics-dubbo 的jar包後,不用修改配置,就直接生效呢?

答案是:固然能夠!

pepper-metrics-dubbo 使用了Dubbo提供的 @Activate 機制。這個註解可用於類或方法上。其做用是可讓Dubbo自動激活此擴展,從而簡化配置。

Provider 爲案例,看一下這個東西在 pepper-metrics-dubbo 裏是咋用的。

@Activate(group = {PROVIDER}) // is here
public class DubboProviderProfilerFilter extends DubboProfilerFilterTemplate {
    @Override
    void afterInvoke(String[] tags, long begin, boolean isError) {
        // ...
    }

    @Override
    void beforeInvoke(String[] tags) {
        // ...
    }
}
複製代碼

首先,若是隻配置 @Activate 註解,不自定義其屬性的話,會無條件自動激活全部擴展點。在咱們的項目中,就是會同時激活 DubboConsumerProfilerFilterDubboProviderProfilerFilter

但在咱們的需求中是不能同時激活兩個擴展點的。若是同時激活,服務提供方和調用方都會同時調用兩個擴展點。而咱們須要的是提供方調用 Provider,調用方調用 Consumer

這能夠經過 group 來實現。定義了 group 以後,就只對特定的group激活了。

在Filter中,有兩個group:

String PROVIDER = "provider";
String CONSUMER = "consumer";
複製代碼

定義爲 PROVIDER 就只對提供方生效,定義爲 CONSUMER 就只對調用方生效,也能夠同時定義,那就同時生效。

這樣一來,只須要依賴 pepper-metrics-dubbo 包便可激活擴展點了。

參考


歡迎關注個人微信公衆號

公衆號
相關文章
相關標籤/搜索