简易版Dubbo方法级性能监控思路及讲解

简易版Dubbo方法级性能监控思路及讲解

完整代码在这里简易版Dubbo方法级性能监控
以下是讲解

一、 要求

在真实业务场景中,经常需要对各个业务接口的响应性能进行监控(常用指标为:TP90、TP99)

下面通过扩展Dubbo的Filter(TPMonitorFilter),完成简易版本 Dubbo 接口方法级性能监控,记录下TP90、TP99请求的耗时情况。

题目要求:

  1. 编写一个Dubbo服务,提供3个方法(methodA、methodB、methodC),每方法都实现了随机休眠0-100ms

  2. 编写一个消费端程序,不断调用Dubbo服务的3个方法(建议利用线程池进行并行调用,确保在1分钟内可以被调用2000次以上)

  3. 利用TPMonitorFilter在消费端记录每个方法的请求耗时时间(异步调用不进行记录)

  4. 每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况

二、要求分析及实现思路

1.分析

是主要的是弄明白TP90、TP99的含义,然后实现出来

TP90、TP99含义

假设你统计了一段时间内访问系统的100次请求数据,他们的响应耗时分别是:1ms、2ms、3ms…100ms。
基于本次统计结果,你系统的 TP90,请求耗时从小到大排列,取第 100*0.9次请求耗时 ,为90ms(代表 90% 请求响应不超过90ms),类似地 TP50=50ms、TP99=99ms。

含义搞明白之后,计算TP90、TP99的逻辑也较简单。

2. 实现思路

  1. 首先创建一个Java Bean即即MonitorMethodInfo用来保存每个方法每次调用的当前时间、耗时信息。
  2. 在TPMonitorFilter中的实现
    1. 集合中记录每次方法调用的监控信息即MethodMonitorInfo
    2. 使有Executors.newSingleThreadScheduledExecutor()每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况。

三、具体实现

1. 项目结构

  • homework-dubbo
    • service-api
    • service-consumer
    • service-provider
    • service-monitor

新添加了service-monitor项目,TPMonitorFilter在这个项目里,然后将serivce-monitor引入到service-consumer里。

2. HelloService实现

提供3个方法methodA、 methodB、 methodC,并且随机休眠0-100ms。

@Service
public class HelloServiceImpl implements HelloService {

    private Random random = new Random();

    private void sleep(int bound) {
        try {
            Thread.sleep(random.nextInt(bound));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String methodA() {
        sleep(100);
        return "methodA被调用了";
    }

    @Override
    public String methodB() {
        sleep(100);
        return "methodB被调用了";
    }

    @Override
    public String methodC() {
        sleep(100);
        return "methodC被调用了";
    }

}

3. HelloComponent

@Component
public class HelloComponent {

    @Reference(timeout = 10000)
    private HelloService helloService;

    /**
     * 调用methodA、methodB、methodC这三个方法
     */
    public String callABC(char c) {
        if ('A' == c) {
            return helloService.methodA();
        } else if ('B' == c) {
            return helloService.methodB();
        } else {
            return helloService.methodC();
        }
    }
}

4. ServiceConsumerMain

循环使用多线程调用methodA、methodB、methodC这三个方法

public class ServiceConsumerMain {

    private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(6);

    public static void main(String[] args) throws IOException {
        System.out.println("-------------");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();
        // 获取消费者组件
        HelloComponent component = context.getBean(HelloComponent.class);
        System.out.println("请输入回车,开始调用服务");
        System.in.read();
        while (true) {
            EXECUTOR_SERVICE.submit(() -> component.callABC('A'));
            EXECUTOR_SERVICE.submit(() -> component.callABC('B'));
            EXECUTOR_SERVICE.submit(() -> component.callABC('C'));
            try {
                Thread.sleep(60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Configuration
    @ComponentScan("com.mh.study.dubbo.component")
    @EnableDubbo(scanBasePackages = "com.mh.study.dubbo.service")
    @PropertySource("classpath:/dubbo-consumer.properties")
    static class ConsumerConfiguration {
    }
}

以上是为测试TP90、TP99对provider和consumer的改造,下面是具体实现TP90、TP99的代码

5. MethodCalledTimeInfo

记录方法调用时间信息的类

@Data
@AllArgsConstructor
public class MethodCalledTimeInfo{

    /**
     * 方法名
     */
    private String methodName;
    /**
     * 调用结束时间
     */
    private long calledEndTime;
    /**
     * 花费时间
     */
    private long spendTime;

    /**
     * 此次调是否在开始时间和结束时间范围内
     * true是false不是
     */
    public boolean endTimeBetween(long startTime, long endTime) {
        return calledEndTime >= startTime && calledEndTime <= endTime;
    }
}

6. TPMonitorFilter实现

给消费添者添加上此过滤器,并交给PDPrintHelper打印PD90、PD99

@Activate(group = CommonConstants.CONSUMER)
public class TPMonitorFilter implements Filter {

    /**
     * PD90、PD99信息打印帮助者
     */
    private PDPrintHelper pdPrintHelper = new PDPrintHelper();

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            long startTime = System.currentTimeMillis();
            // 执行方法
            Result result = invoker.invoke(invocation);
            long endTime = System.currentTimeMillis();
            // 保存本次方法调用时间信息,并触发打印PD90、PD99
            pdPrintHelper.put(new MethodCalledTimeInfo(invocation.getMethodName(), startTime, endTime - startTime));
            System.out.println(result.getValue() + ", 耗时: " + (endTime - startTime) + "ms");
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}

在service-monitor下创建文件resources\META-INF\dubbo\org.apache.dubbo.rpc.Filter

内容为:

monitorFilter=com.mh.study.dubbo.filter.TPMonitorFilter

7. PDPrintHelper

由此类实现打印PD90、PD99

public class PDPrintHelper {

    /**
     * 方法每次被调用时间,以方法名分组
     * key是方法名,value是方法被调用的时间信息
     */
    private final Map<String, List<MethodCalledTimeInfo>> calledTimeInfoMap = new HashMap<>();

    /**
     * true表示已开始打印
     */
    private volatile boolean startedPrint;

    public void put(MethodCalledTimeInfo monitorMethodInfo) {
        // 当发起第一次调用后,开始每隔5s打印TP90和TP99
        triggerPrint();
        String methodName = monitorMethodInfo.getMethodName();
        if (!calledTimeInfoMap.containsKey(methodName)) {
            calledTimeInfoMap.put(methodName, new ArrayList<>());
        }
        calledTimeInfoMap.get(methodName).add(monitorMethodInfo);
    }

    /**
     * 当发起第一次调用后,开始每隔5s打印TP90和TP99
     */
    private void triggerPrint() {
        if (!startedPrint) {
            startedPrint = true;
            Executors.newSingleThreadScheduledExecutor()
                    .scheduleWithFixedDelay(this::printTP, 5, 5, TimeUnit.SECONDS);
        }
    }

    private void printTP() {
        if (calledTimeInfoMap.size() == 0) {
            return;
        }
        LocalDateTime now = LocalDateTime.now();
        long currentTime = System.currentTimeMillis();
        long oneMinuteAgoTime = currentTime - 60000;
        calledTimeInfoMap.forEach((methodName, calledTimeInfos) -> {
            List<MethodCalledTimeInfo> sortedList = calledTimeInfos.stream()
                    // 筛选出一分钟以内的调用
                    .filter(info -> info.endTimeBetween(oneMinuteAgoTime, currentTime))
                    // 按耗时升序排序
                    .sorted(Comparator.comparingLong(MethodCalledTimeInfo::getSpendTime))
                    .collect(Collectors.toList());
            long tp90 = sortedList.get((int) (sortedList.size() * 0.9)).getSpendTime();
            long tp99 = sortedList.get((int) (sortedList.size() * 0.99)).getSpendTime();
            System.out.printf("【%s】-【%s】,TP90: %sms,TP99: %sms\n", now, methodName, tp90, tp99);
        });
    }

}

四、结果验证

methodA被调用了, 耗时: 54ms
methodC被调用了, 耗时: 67ms
methodA被调用了, 耗时: 123ms
methodC被调用了, 耗时: 126ms
methodB被调用了, 耗时: 25ms
methodB被调用了, 耗时: 102ms

省略若干行日志...

methodA被调用了, 耗时: 32ms
methodC被调用了, 耗时: 84ms
methodA被调用了, 耗时: 46ms
【2022-02-14T22:00:08.140874500】-【methodA】,TP90: 98ms,TP99: 123ms
【2022-02-14T22:00:08.140874500】-【methodB】,TP90: 88ms,TP99: 160ms
【2022-02-14T22:00:08.140874500】-【methodC】,TP90: 87ms,TP99: 126ms
methodC被调用了, 耗时: 60ms
methodB被调用了, 耗时: 63ms
methodA被调用了, 耗时: 11ms
methodC被调用了, 耗时: 44ms
methodB被调用了, 耗时: 64ms

省略若干行日志...

methodC被调用了, 耗时: 50ms
methodB被调用了, 耗时: 12ms
methodA被调用了, 耗时: 22ms
methodC被调用了, 耗时: 22ms
methodC被调用了, 耗时: 4ms
methodA被调用了, 耗时: 35ms
methodB被调用了, 耗时: 35ms
【2022-02-14T22:00:13.150810400】-【methodA】,TP90: 88ms,TP99: 102ms
【2022-02-14T22:00:13.150810400】-【methodB】,TP90: 93ms,TP99: 102ms
【2022-02-14T22:00:13.150810400】-【methodC】,TP90: 93ms,TP99: 103ms

省略若干行日志...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值