🌹🌹 If you find my article helpful, remember to star on GitHub 🌹🌹

🌹 🌹 GitHub_awesome – it – blog 🌹 🌹


I recently collaborated with colleagues to develop a small but beautiful application performance monitoring framework (Pepper-metrics).

One requirement is to collect the performance data of Dubbo’s interface responses on the Provider and Consumer sides for storage in the DataSource or for Printer use.

In this context, we need to monitor every request and response on both the Provider and Consumer side. Can be extended in Dubbo org. Apache. Dubbo. RPC. The Filter interface implementation.

0 org. The apache. Dubbo. RPC. The Filter is introduced

Filter can be understood as a call procedure interceptor, which takes effect every time a method calls the interceptor.

User-defined filters are executed after existing filters by default.

1 Pepper-Metrics-Dubbo Filter implementation

In our project, this submodule is named pepper-metrics-Dubbo, and the structure of this module is as follows:

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
Copy the code

In Pepper – Metrics – Dubbo, DubboProfilerFilterTemplate class implements the org. Apache. Dubbo. RPC. The Filter interface.

This is a template class that defines a generic implementation of the filter.invoke () method. Since different collectors are required for providers and consumers when collecting profiles specifically, Through its subclasses DubboProviderProfilerFilter here and DubboConsumerProfilerFilter implementation respectively.

The above class relationship can be described in the following figure:

DubboProfilerFilterTemplate outlined implementation is as follows:

public abstract class DubboProfilerFilterTemplate implements Filter {

    // Provider collector
    static final Stats PROFILER_STAT_IN = Profiler.Builder
            .builder()
            .name("app.dubbo.request.in")
            .build();
    // Collector for 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 {
        / / to omit... Some necessary preparations

        // Template method, before trace...
        beforeInvoke(tags);
        try {
            Result result = invoker.invoke(invocation);
            // Record whether the call reported an error
            if (result == null || result.hasException()) {
                isError = true;
            }

            specialException = false;

            return result;
        } finally {
            if (specialException) {
                isError = true;
            }
            // Template method, after trace...afterInvoke(tags, begin, isError); }}abstract void afterInvoke(String[] tags, long begin, boolean isError);

    abstract void beforeInvoke(String[] tags);
}
Copy the code

The two implementation classes are as follows:

// Provider
@Activate(group = {PROVIDER})
public class DubboProviderProfilerFilter extends DubboProfilerFilterTemplate {
    @Override
    void afterInvoke(String[] tags, long begin, boolean isError) {
        // Record the response implementation
        PROFILER_STAT_IN.observe(System.nanoTime() - begin, TimeUnit.NANOSECONDS, tags);
        // The number of concurrent requests decreases
        PROFILER_STAT_IN.decConc(tags);
        // Record the number of errors
        if(isError) { PROFILER_STAT_IN.error(tags); }}@Override
    void beforeInvoke(String[] tags) {
        // The number of concurrent requests increasesPROFILER_STAT_IN.incConc(tags); }}Copy the code
// 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); }}Copy the code

After writing the implementation class, you need to configure the extension file for Dubbo in the Resources directory of your project.

Under the resources to create a meta-inf/dubbo/org. Apache. Dubbo. RPC. The Filter files, content is as follows:

dubboProviderProfiler=com.pepper.metrics.integration.dubbo.DubboProviderProfilerFilter
dubboConsumerProfiler=com.pepper.metrics.integration.dubbo.DubboConsumerProfilerFilter
Copy the code

This allows Dubbo to scan for custom extension points.

2 Use custom Filter

Next, I need to configure the custom extension point to Dubbo, and tell Dubbo I want to use this Filter, and configure it in Provider and Consumer respectively:

First look at the 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"/>

    <! -- Configure custom extension points here -->
    <dubbo:service filter="default,dubboProviderProfiler" interface="com.pepper.metrics.sample.dubbo.spring.api.DemoService" ref="demoService" />

</beans>
Copy the code

Note: Default represents the existing extension point, and dubboProviderProfiler is our custom extension point. This configuration means that our custom extension point is executed after the existing extension point.

Also, configure custom extension points on the Consumer side:

<?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"/>

    <! -- Configure custom extension points here -->
    <dubbo:reference filter="default,dubboConsumerProfiler" id="demoService" check="true" interface="com.pepper.metrics.sample.dubbo.spring.api.DemoService" />

</beans>
Copy the code

3 Customize automatic activation of extension points

As we know from the above, our custom extension point has to be modified to take effect, which is code intrusion. Can we introduce the pepper-metrics- Dubbo jar without modifying the configuration?

The answer is: yes!

Pepper -metrics- Dubbo uses the @activate mechanism provided by Dubbo. This annotation can be used on a class or method. This allows Dubbo to activate this extension automatically, simplifying configuration.

Using Provider as an example, see how this thing works in 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) {
        // ...}}Copy the code

First, if you configure only the @Activate annotation without customizing its attributes, all extension points will automatically be activated unconditionally. In our project, just will activate DubboConsumerProfilerFilter and DubboProviderProfilerFilter at the same time.

However, it is not possible to activate two extension points at the same time in our requirements. If activated at the same time, both service providers and callers invoke both extension points. What we need is for the Provider to call Provider and the caller to call Consumer.

This can be done through groups. Once groups are defined, only specific groups are activated.

In Filter, there are two groups:

String PROVIDER = "provider";
String CONSUMER = "consumer";
Copy the code

Defining it as PROVIDER only applies to the PROVIDER, defining it as CONSUMER only applies to the caller, or both.

This way, you only need to rely on the pepper-metrics-Dubbo package to activate the extension point.

reference

  • Dubbo.apache.org/zh-cn/docs/…
  • Dubbo.apache.org/zh-cn/docs/…