文章目录

  • 1. dubbo的Qos
  • 2.dubbo Qos简单使用
  • 2.1 ls
  • 2.2 online
  • 2.3 offline
  • 2.4 help
  • 2.5 quit
  • 3.dubbo Qos 源码剖析
  • 3.1 Qos服务器创建时机
  • 3.2 QosProtocolWrapper
  • 3.3 com.alibaba.dubbo.qos.server.Server
  • 3.4 com.alibaba.dubbo.qos.server.handler.QosProcessHandler
  • 3.4 BaseCommand
  • 3.4.1 ls
  • 3.4.2 online
  • 3.4.3 offline
  • 3.4.4 help
  • 3.4.5 quit
  • 4.总结

注:本文基于dubbo v2.6.1

1. dubbo的Qos

QoS的英文全称为"Quality of Service",中文名为"服务质量"。在dubbo 2.5.8 新版本增加了 QOS 模块,提供了新的 telnet 命令支持。dubbo管它叫在线运维命令,我们可以通过它能够看到服务提供者状态,服务调用者状态,现在dubbo提供了 ls , online,offline,help ,quit命令。

命令

用途

ls

能够列出来该实例服务提供者与调用者状态

online

服务上线,可以指定某个接口,也可以什么也不指定,这样就是全部

offline

服务下线,可以指定某个接口,也可以什么也不指定,这样就是全部

help

查看命令的用途,不带参数显示全部命令,带参数只显示指定的

quit

退出Qos

2.dubbo Qos简单使用

我们可以在dubbo的配置文件配置参数,我这里是使用的xml的形式,其他形式可以看下官网文档:官网文档(文档讲的很详细,而且是中文)

<dubbo:application name="dubbo-consumer">
    <dubbo:parameter key="qos.enable" value="true"/>
    <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
    <dubbo:parameter key="qos.port" value="8108"/>
</dubbo:application>

qos.enable :表示是否开始Qos

qos.accept.foreign.ip: 允许访问的ip,缺省就是false,表示任何ip都可访问

qos.port: Qos提供服务的端口

接下来我启动服务,然后在终端上使用telnet的方式连接dubbo Qos

dubbo接口拦截器 dubbo端口_ide

2.1 ls

可以看到服务提供服务与调用服务的状态

这个 Provider Service Name 就是服务提供者名字 , PUB 就是状态 N是未注册,就是没有注册到注册中心,其实服务下线功能就是从注册中心unregister ,Y表示服务在线,就是注册到注册中心了。

dubbo接口拦截器 dubbo端口_java_02

2.2 online

服务上线,就是将服务注册到注册中心上去,后面可以带个参数,参数是指定具体上线的接口,不带参数表示全部服务接口(这个得是你标识的,比如说使用了dubbo @Service注解)都注册到注册中心去。

我们ls小节的时候可以看到com.xuzhaocai.dubbo.provider.IUserInfoService 是N,我们可以使用online来进行服务上线

dubbo接口拦截器 dubbo端口_后端_03


dubbo接口拦截器 dubbo端口_dubbo接口拦截器_04


我们可以看到服务状态是Y了,这其实就是注册中心 调用register(),我们在后面可以看到具体代码实现。

2.3 offline

offline 是服务下线,跟online一个使用方法,这里就不再赘述了。

2.4 help

帮助,可以指定具体的cmd,这样就显示指定cmd的帮助

dubbo接口拦截器 dubbo端口_后端_05


不指定的话显示全部的命令

dubbo接口拦截器 dubbo端口_服务提供者_06

2.5 quit

退出Qos,你不用这个quit可以试试,看看能不能退出去,我mac直接使用ctrl+c 反正不行。
注: 我这里就不再详细说明这个用法了,这东西官网文档很详细,而且使用也简单,主要是场景 ,直接源码搞起

3.dubbo Qos 源码剖析

3.1 Qos服务器创建时机

其实在dubbo进行服务暴露与服务引用的时候,我们都见过Exporter<?> exporter = protocol.export(wrapperInvoker); (com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol)或者refprotocol.refer(interfaceClass, urls.get(0));(com.alibaba.dubbo.config.ReferenceConfig#createProxy) ,我们知道这个protocol成员是这个样子

dubbo接口拦截器 dubbo端口_ide_07


根据dubbo spi 自适应的规则,实际上执行的是 url中protocol 参数值对应的那个实现类,我们这个场景下就是RegistryProtocol 实现类,然后dubbo spi 还会帮你setter 注入 ,wrapper 包装

我们可以看下都有哪些Protocol 包装实现类

dubbo接口拦截器 dubbo端口_ide_08


v2.6.1 这个版本就这些。

我们获得的RegistryProtocol对象,其实外面是包了这几个wrapper类,就像下图这个样子(当然这个顺序不一定是这个样子的,他这边没有对顺序做要求)

dubbo接口拦截器 dubbo端口_ide_09


所以我们在服务暴露或者服务引用的时候,就会走这个QosProtocolWrapper类。

3.2 QosProtocolWrapper

dubbo接口拦截器 dubbo端口_java_10


我们可以看到export与refer方法,判断如果protocol=registry,接着进入这个startQosServer(url)

看下这 startQosServer方法。

dubbo接口拦截器 dubbo端口_服务提供者_11


这个方法中我们可以看到就是判断qos.enable 启动参数,然后 判断是否已经启动,这个玩意一个服务实例就只能启动一次,再就是获得一个Server对象,将用户设置的一些参数值设置进去,然后调用start方法。

我们来看下这个Server类

3.3 com.alibaba.dubbo.qos.server.Server

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_12


我们这里是创建了一个netty的server,开始的时候使用cas判断启动状态,添加了一个QosProcessHandler消息处理类,我们的命令就是到这里面处理的。我们看下这个QosProcessHandler 类,传入参数就是welcome欢迎语跟允许的ip。

我们来看下的这个QosProcessHandler消息处理类

3.4 com.alibaba.dubbo.qos.server.handler.QosProcessHandler

当消息连接上的时候,会调用channelActive方法,我们可以看到,它将这个welcome 欢迎语与 dubbo> 这个开始头发出去了。

dubbo接口拦截器 dubbo端口_后端_13


也就是这个东西

dubbo接口拦截器 dubbo端口_服务提供者_14


接着我们再输入命令的时候,就会到decode 方法中,首先判断buffer里面有没有能读的字节,如果没有直接返回,获取一个字节,判断是不是http请求,因为Dubbo Qos 是支持http 与telnet的。

dubbo接口拦截器 dubbo端口_后端_15


这里就会相应的添加handler,我们可以看到,他这个handler是动态添加与删除的,每一次访问都要添加与删除。

我们这里使用的是telnet ,我们看下telnet的是怎样实现的,http 的也就同理了。

接下来我们看下这个TelnetProcessHandler 处理类,那四个都是netty的,做相应辅助功能的,比如说使用行解析,使用utf-8解码,然后空闲时间,TelnetProcessHandler 是主要处理我们命令的

@Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

        if (StringUtils.isBlank(msg)) {// 是否是空消息
            ctx.writeAndFlush(QosProcessHandler.prompt);// 将dubbo>   写回去
        } else {
            CommandContext commandContext = TelnetCommandDecoder.decode(msg);
            commandContext.setRemote(ctx.channel());// 设置channel

            try {
                String result = commandExecutor.execute(commandContext);
                if (StringUtils.equals(QosConstants.CLOSE, result)) {// 是否返回close
                    ctx.writeAndFlush(getByeLabel()).addListener(ChannelFutureListener.CLOSE);
                } else {// 将结果写回去   xxx /r/n  dubbo>
                    ctx.writeAndFlush(result + QosConstants.BR_STR + QosProcessHandler.prompt);
                }
            } catch (NoSuchCommandException ex) {
                ctx.writeAndFlush(msg + " :no such command");
                ctx.writeAndFlush(QosConstants.BR_STR + QosProcessHandler.prompt);
                log.error("can not found command " + commandContext, ex);
            } catch (Exception ex) {
                ctx.writeAndFlush(msg + " :fail to execute commandContext by " + ex.getMessage());
                ctx.writeAndFlush(QosConstants.BR_STR + QosProcessHandler.prompt);
                log.error("execute commandContext got exception " + commandContext, ex);
            }
        }
    }

这里首先判断消息是不是空,空消息就将 dubbo>这个返回给客户端

然后将消息解析成 CommandContext, 也就是CommandContext commandContext = TelnetCommandDecoder.decode(msg); 这句,就是将消息解析成cmd与参数,调用CommandContext工厂创建CommandContext 对象。

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_16


接着就是使用CommandExecutor 执行这个命令,也就是这句String result = commandExecutor.execute(commandContext); 我们来看下这个DefaultCommandExecutor的execute方法,先是通过命令获取对应的BaseCommand实现类,没有找到对应的话就抛出NoSuch的异常。有的话直接调用execute执行。

dubbo接口拦截器 dubbo端口_服务提供者_17


这个BaseCommand我们后面再说,这个执行结果返回,接着回到TelnetProcessHandler 的channelRead0方法中,如果这个执行结果是close的话,就写回"BYE! ";这个字符串,并且添加ChannelFutureListener.CLOSE 这个listener。

如果是正常执行结果的话,写回去就可以了。

现在我们来看下这个BaseCommand的定义以它的子类们

3.4 BaseCommand

这个是就是命令的接口,是基于dubbo spi的,所有Qos命令都要实现它,然后重写execute方法。

dubbo接口拦截器 dubbo端口_服务提供者_18


看下它的实现类

dubbo接口拦截器 dubbo端口_ide_19


下面我们就挨个看看

3.4.1 ls

ls 这个命令是获取该实例服务提供者接口与调用者接口及状态。

我们来看看代码实现

dubbo接口拦截器 dubbo端口_java_20


调用了listProvider()获取了服务提供者接口,调用了服务调用者接口。

我们这里拿获取服务提供者接口看看:

public String listProvider() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("As Provider side:
");//通过ApplicationModel获取所有的provider,这个是服务注册上的时候添加到ApplicationModel 里面的
        Collection<ProviderModel> ProviderModelList = ApplicationModel.allProviderModels();
        TTable tTable = new TTable(new TTable.ColumnDefine[]{
                new TTable.ColumnDefine(TTable.Align.MIDDLE),
                new TTable.ColumnDefine(TTable.Align.MIDDLE)
        });
        //Header
        tTable.addRow("Provider Service Name", "PUB");
        //Content
        for (ProviderModel providerModel : ProviderModelList) {
            tTable.addRow(providerModel.getServiceName(), isReg(providerModel.getServiceName()) ? "Y" : "N");
        }
        stringBuilder.append(tTable.rendering());

        return stringBuilder.toString();
    }

这边有两个点: 一个是通过ApplicationModel 获取所有的provider,这个ApplicationModel 类,里面其实维护了几个map,其中就有本实例服务提供者与服务调用者接口列表,当我们在暴露服务完成的时候,就会向ApplicationModel 这个类里面添加对应的服务提供者的信息,也就是走的这个方法

dubbo接口拦截器 dubbo端口_后端_21


每当我们服务引用完,创建完proxy代理类的时候,就会往ApplicationModel 这个类里面添加对应的服务调用者的信息,也就是走的这个方法

dubbo接口拦截器 dubbo端口_服务提供者_22


所以我们就能拿到本实例的服务提供者接口了。接着往下看就是创建TTable ,是两列的,然后遍历服务提供者接口列表添加行。

到最后就是rendering 渲染了,在循环的时候其实还有一个点,使用isReg(providerModel.getServiceName()) “Y” : “N”) 是否在线,这里面判断的其实还是invoker包装类的isReg,注册到注册中心后设置这个标识。

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_23


同理本实例服务提供者也是这个样子获取的,到这里我们这个ls命令处理就完成了。

3.4.2 online

com.alibaba.dubbo.qos.command.impl.Online 服务上线。

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_24


这个online 命令使用参数的,如果用户没有指定,就是.*,在下面匹配上就会匹配到。

首先是判断参数,接着就是从ApplicationModel 获取本实例服务提供者列表,遍历,看看是否符合你传过来的参数,,符合就设置hasService = true,接着就是根据serviceName从本地注册表中获取对应的ProviderInvokerWrapper列表,遍历判断如果对应的状态是在线的(这里还是判断的isReg),就跳过,否则获取对应的注册中心,进行注册,同时将isReg状态设置成true。

3.4.3 offline

com.alibaba.dubbo.qos.command.impl.Offline 服务下线,我们看下代码,它这个与online差不多,它这个循环判断服务没有在线就跳过去,在线的就调用注册中心的unregister()方法进行服务下线,同时设置isReg服务在线状态为false。

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_25

3.4.4 help

com.alibaba.dubbo.qos.command.impl.Help 帮助文档作用,可以传参数 指定需要帮助的命令,也可以不指定,那就是显示全部命令的帮助。直接上代码

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_26


我们看下这行所有命令的,这个指定命令的原理也差不多

dubbo接口拦截器 dubbo端口_java_27


就是使用CommandHelper.getAllCommandClass() 获取所有的BaseCommand 子类class对象,他这个使用dubbo spi获取所有子类的

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_28


然后获取子类上面的@Cmd注解,就像offline上面这个,name就是命令,summary就是说明 ,example 就是列子

dubbo接口拦截器 dubbo端口_后端_29

3.4.5 quit

com.alibaba.dubbo.qos.command.impl.Quit 退出 ,这就是直接返回了close,然后TelnetProcessHandler就会处理这个close进行退出。

dubbo接口拦截器 dubbo端口_dubbo接口拦截器_30

4.总结

到这里我们dubbo 在线运维Qos 就解析完成了,概括一下,首先是讲了 dubbo Qos ,然后简单使用了一下,最后就是咱们的源码解析了,从Qos服务器启动时机到每一个commad命令的执行原理。这个Qos 使用简单,主要还是它的使用场景,正如dubbo 官方文档说的,服务上线 服务下线的使用场景。