简易版Dubbo方法级性能监控(实现TP90、TP99)

一、具体要求

  • 在真实业务场景中,经常需要对各个业务接口的响应性能进行监控(常用指标为:TP90、TP99)
  • 通过扩展Dubbo的Filter(TPMonitorFilter),完成简易版本 Dubbo 接口方法级性能监控,记录下TP90、TP99请求的耗时情况
要求说明
1提供一个Dubbo服务,提供3个方法(eat、sleep、beatBeanBean),每方法都实现了随机休眠0-100ms
2编写一个消费端程序,不断调用Dubbo服务的3个方法(建议利用线程池进行并行调用,确保在1分钟内可以被调用2000次以上)
3利用TPMonitorFilter在消费端记录每个方法的请求耗时时间(异步调用不进行记录)
4每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况

二、实现思路介绍

在这里插入图片描述

说明:api/consumer/provider 三个模块的内容 在这里就不多介绍了,感兴趣的可以到下方的源码下载中,下载没有过滤器的基础案例

三、自定义过滤器,实现方法拦截,统计方法执行时间,存储方法执行信息,计算TP90/99

步骤1:自定义拦截器TPNMonitorFilter,方法执行就拦截,利用TPMonitorFilter在消费端记录每个方法的请求耗时时间

TPNMonitorFilter 实现接口Filter 重写invoke方法,在方法内部进行方法拦截,并计算方法耗时

//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{

	/**
     * 方法执行就拦截:利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
     * @param invoker
     * @param invocation
     * @return
     * @throws RpcException
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {		
   	   //(具体实现看下方功能实现代码) 
       //获取方法执行的起始时间
       //执行方法
       //计算方法执行的耗时
       //将当前的方法添加到全局map中,在map中指定一个集合进行记录当前方法执行情况
    }

}
步骤2:创建一个集合存储三个方法执行得相关信息(包括:方法名称,方法执行耗时,方法执行结束时间)
//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{

    /**
     * 存储方法执行得相关信息(方法名称,方法执行耗时,方法执行结束时间)
     */
    Map<String, List<MethodInfo>> methodTimes = new ConcurrentHashMap<>();

}

自定义bean存储方法执行信息

public class MethodInfo {
    // 方法名
    private  String name;
    // 执行耗时
    private  long   times;
    // 结束时间
    private  long   endTimes;

    public MethodInfo() {
    }

    public MethodInfo(String name, long times,long endTimes) {
        this.name = name;
        this.times = times;
        this.endTimes = endTimes;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getTimes() {
        return times;
    }

    public void setTimes(long times) {
        this.times = times;
    }

    public long getEndTimes() {
        return endTimes;
    }

    public void setEndTimes(long endTimes) {
        this.endTimes = endTimes;
    }
}
步骤3:自定义线程,计算tp90/tp99

TPNMonitorFilter 实现接口Runnable重写run方法,在方法内部进行方法拦截,并计算方法耗时

//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{
	
	//在消费者端进行激活此拦截器
	@Activate(group = {CommonConstants.CONSUMER})
	public class TPNMonitorFilter implements Filter ,Runnable{
		/**
	     * 线程方法
	     */
	    @Override
	    public void run() {
	       //计算并打印最近1分钟内每个方法的TP90、TP99的耗时情况(具体实现看下方功能实现代码)
	    }
	
	}
}
步骤4:配置filter的spi

在这里插入图片描述
此时整个自定义Filter的整理结构就介绍完了


功能实现1:每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
//每隔5s调用一次下方的run方法,统计计算一次1分钟内每个方法的TP90、TP99的耗时情况
public TPNMonitorFilter(){
        // 创建定时线程,每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
        Executors.newSingleThreadScheduledExecutor()
                .scheduleWithFixedDelay(this, 5,5, TimeUnit.SECONDS);
         }
}

线程具体执行操作:

	 /**
     * 线程方法
     */
    @Override
    public void run() {
        Date date = new Date();
        SimpleDateFormat sdf  = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String dateStr = sdf.format(date);
        /**
         * 线程使用情况
         */
        for(Map.Entry<String,List<MethodInfo>> methodInfos : methodTimes.entrySet()){
            System.out.println(dateStr+ methodInfos.getKey() +"的TP90:" + getTP(methodInfos.getValue(),0.9) + "毫秒,"
                    + "TP99:" + getTP(methodInfos.getValue(),0.99)+ "毫秒" );
        }
    }

TP90、TP99的耗时情况计算:

/**
     * 后续会每隔5s调用一次此方法,打印最近1分钟内每个方法的TP90、TP99的耗时情况
     *
     * 计算tp90和tp99
     * @param methodInfos
     * @param rate 代表百分比 90 传入 0.9 即可  99 就是 0.99
     * @return
     */
    private  long  getTP(List<MethodInfo> methodInfos,double  rate){
        // 构建一个临时集合保存 用于满足1一分钟之内的数据
        List<MethodInfo>  sortInfo = new ArrayList<>();
        // 计算最近一分钟的TP90 和 TP99
        long  endTime = System.currentTimeMillis();
        long  startTime = System.currentTimeMillis() - 60000;

        // 遍历列表集合
        int  length = methodInfos.size();
        for (int i=0;i<length;i++){
            MethodInfo  methodInfo = methodInfos.get(i);
            if (methodInfo.getEndTimes() >= startTime && methodInfo.getEndTimes() <= endTime){
                //将满足条件的方法信息存储到临时集合中
                sortInfo.add(methodInfo);
            }
        }

        //对满足1一分钟之内的数据进行排序
        sortInfo.sort(new Comparator<MethodInfo>() {
            @Override
            public int compare(MethodInfo o1, MethodInfo o2) {
                if(o1.getTimes() > o2.getTimes()){
                    return  1;
                }else if(o1.getTimes() < o2.getTimes()){
                    return -1;
                }else{
                    return  0;
                }

            }
        });

        //获取当前排序后集合中的指定百分比数值的位置,此位置存储的数据就是当前计算的tp90/99
        int  index = (int)(sortInfo.size() * rate);

        return sortInfo.get(index).getTimes();
    }
功能实现2:方法执行就拦截,利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
   /**
     * 方法执行就拦截:利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
     * @param invoker
     * @param invocation
     * @return
     * @throws RpcException
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 获取执行方法名
        Result result = null;
        Long takeTime = 0L;
        try
        {
            //获取方法执行的起始时间
            Long startTime = System.currentTimeMillis();
            //执行方法
            result = invoker.invoke(invocation);

            if (result.getException() instanceof Exception) {
                throw new Exception(result.getException());
            }
            //计算方法执行的耗时
            takeTime = System.currentTimeMillis() - startTime;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return result;
        }

        System.out.println("method="+invocation.getMethodName() + "消耗的时间:"+takeTime +"毫秒");
        String methodName = invocation.getMethodName();

        List<MethodInfo> methodInfos =  methodTimes.get(methodName);

        //方法执行第一次的时候,创建空集合进行存储
        if (methodInfos == null){
            methodInfos = new ArrayList<>();
            methodTimes.put(methodName,methodInfos);
        }

        //将当前的方法添加到map中指定的记录当前方法执行情况的集合中
        methodInfos.add(new MethodInfo(invocation.getMethodName(),takeTime,System.currentTimeMillis()));

        return  result;
    }

运行程序

  1. 启动zookeepr
    在这里插入图片描述2. 启动provider
    在这里插入图片描述3. 启动consumer
    在这里插入图片描述

源码下载

案例地址
基础案例下载地址:gitee代码下载
功能实现案例下载地址:gitee代码下载
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿城大饼

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值