深度解析dubbo过滤器之TraceFilter

本文基于dubbo v2.6.x


在讲解TraceFilter 之前我们需要演示一下dubbo telnet 服务治理命令trace。

1. dubbo telnet trace 演示

如果对dubbo telnet 服务治理命令不熟悉的同学可以去dubbo官网文档学习下:官网文档链接
我们这里演示 trace的使用,我们可以使用trace能干什么呢,主要是监听 某个接口的任意方法或者某个方法 n次,然后返回对应的执行时间。
借用下dubbo官网对于trace的解释:
在这里插入图片描述
我们这里演示下,我这里现在有个服务提供者跟服务调用者,然后我这telnet 一下 服务调用者,看下某个接口的某个方法的执行时间。
注意: telnet 的ip就是服务提供者的机器ip ,端口是dubbo 端口,就是你配置的那个端口,我这里是18108

<dubbo:protocol accesslog="true" name="dubbo" port="18108"/>

我们看下使用:
在这里插入图片描述
这时候你再回下车,
接着 我就输入 trace 命令了

trace com.xuzhaocai.dubbo.provider.IHelloProviderService getName 3 

**注意: com.xuzhaocai.dubbo.provider.IHelloProviderService 这个是 接口 ,getName 方法名 ,3 这个是监听次数 ,监听3次就不监听了 **
它的使用也就是这个样子的 : trace [service] [method] [times]
如果你不写method 直接就是监听 这个接口的任意一个方法
times 这个监听次数,你不写就是1次。
我们来看下我的监听截图
在这里插入图片描述
我们看到,监听三次后就不再监听了,每次打印会将你的接口名+方法名+参数+结果值+ 执行时间打印出来。其实我们主角TraceFilter 就与这个功能实现有关,下面我们看下TraceFilter。

2. TraceFilter解析

我们在解析TraceFilter 源码之前先讲解一下trace 大体的实现流程,我们telnet 命令会被dubbo handler 单读处理,然后将trace命令解析出来,交给TraceTelnetHandler ,然后它经过一顿操作,找到接口暴露的invoker,然后再向TraceFilter中添加这个tracer,其实就是将要监听接口名,方法名,监听次数,还有channel 给TraceFilter,接下来就是调用的时候收集 执行时间,将执行时间 找到对应的channel 然后发送给telnet客户端。
接下来我们就看下这个TraceFilter ,首先看下 添加tracer与移除tracer方法

// 缓存
    private static final ConcurrentMap<String, Set<Channel>> tracers = new ConcurrentHashMap<String, Set<Channel>>();
    // 添加Tracer
    public static void addTracer(Class<?> type, String method, Channel channel, int max) {
        channel.setAttribute(TRACE_MAX, max);// trace.max
        channel.setAttribute(TRACE_COUNT, new AtomicInteger());// 统计count
        // 拼装key,如果method没有的话就使用type的全类名, 如果有method话就是 全类名.方法名
        String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
        Set<Channel> channels = tracers.get(key);// 从缓存中获取
        if (channels == null) {// 如果有找到对应的channel
            tracers.putIfAbsent(key, new ConcurrentHashSet<Channel>());
            channels = tracers.get(key);
        }
        channels.add(channel);// 添加到set集合中
    }

上面这段代码就是添加tracer,先解释下参数Class<?> type 这个就是你监听接口class,method: 监听的方法,channel:你发起telnet那个通道,max: 这个就是监听的最大次数。
首先是往channel 塞了 trace.max 与trace.count 两个属性值,这个 trace.max 就是最大监听次数,trace.count 就是计数器,记录调用了几次了。接着就是拼装key,如果method没有的话就使用type的全类名, 如果有method话就是 全类名.方法名 ,先从tracers这个map里面查找有没有对应的channels,没有找到就创建set集合,最后添加到这个集合中。

 // 移除tracer
    public static void removeTracer(Class<?> type, String method, Channel channel) {
        channel.removeAttribute(TRACE_MAX);// 先从channel 中移除这两个属性
        channel.removeAttribute(TRACE_COUNT);

        // 拼装key
        String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
        Set<Channel> channels = tracers.get(key);
        if (channels != null) {
            channels.remove(channel);// 移除
        }
    }

这个是移除tracer的方法,先移除channel中的这两个属性,拼装key,根据key从tracers中获取channels,然后移除对应的channel。
接下来我们就要看下invoke方法了


    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();// 开始时间
        Result result = invoker.invoke(invocation);
        long end = System.currentTimeMillis();// 结束时间
        if (tracers.size() > 0) {
            // 拼装key,先使用全类名.方法名的 形式
            String key = invoker.getInterface().getName() + "." + invocation.getMethodName();// 接口全路径.方法名
            Set<Channel> channels = tracers.get(key);// 获取对应tracers
            if (channels == null || channels.isEmpty()) {// 没有对应的tracer 就使用 接口全类名做 key
                key = invoker.getInterface().getName();// 接口名
                channels = tracers.get(key);// 使用全类名key再获取一遍
            }
            if (channels != null && !channels.isEmpty()) {//
                for (Channel channel : new ArrayList<Channel>(channels)) {//遍历这堆channel
                    if (channel.isConnected()) {//不是关闭状态的话
                        try {
                            int max = 1;
                            // 从channel中获取trace.max属性
                            Integer m = (Integer) channel.getAttribute(TRACE_MAX);//获取trace.max属性
                            if (m != null) {//如果 max 是null的话,max=m
                                max = (int) m;
                            }
                            int count = 0;

                            // 这个其实是个计数器
                            AtomicInteger c = (AtomicInteger) channel.getAttribute(TRACE_COUNT);// trace.count
                            if (c == null) {// 没有就新建然后设置进去
                                c = new AtomicInteger();
                                channel.setAttribute(TRACE_COUNT, c);
                            }
                            count = c.getAndIncrement();// 调用次数+1
                            if (count < max) {// 当count小于max的时候

                                // 获取那个终端上的头 ,这个不用纠结
                                String prompt = channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);

                                // 发送 耗时信息
                                channel.send("\r\n" + RpcContext.getContext().getRemoteAddress() + " -> "
                                        + invoker.getInterface().getName()
                                        + "." + invocation.getMethodName()
                                        + "(" + JSON.toJSONString(invocation.getArguments()) + ")" + " -> " + JSON.toJSONString(result.getValue())
                                        + "\r\nelapsed: " + (end - start) + " ms."
                                        + "\r\n\r\n" + prompt);
                            }
                            if (count >= max - 1) {// 当调用总次数超过 max的时候
                                channels.remove(channel);// 就将channel移除
                            }
                        } catch (Throwable e) {
                            channels.remove(channel);
                            logger.warn(e.getMessage(), e);
                        }
                    } else {
                        channels.remove(channel);
                    }
                }
            }
        }
        // 返回result
        return result;
    }

我们可以看到在执行前后都记录了时间戳,然后判断如果有tracer的话,就要拼装key,先拼装全类型.方法名形式的,进行get,如果结果是空的话,就使用接口名获取channels。如果channels不是空,就遍历,如果channel是连接状态的话,先判断trace.max与trace.count。然后对trace.count这个计数器自增1,表示调用次数+1,如果调用次数小于 最大调用次数(就是咱们设置的那个times)的话,就向channel中发送 “接口+方法名+参数+结果值+ 执行时间”给我们。如果count大于等于max-1的话 就移除channel。
最后就是将执行结果result返回了。
好了,以上就是TraceFilter的全部的,还是很简单的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

$码出未来

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

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

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

打赏作者

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

抵扣说明:

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

余额充值