dubbo系列之-qos运维-2021-01-17

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dubbo系列之-qos运维-2021-01-17相关的知识,希望对你有一定的参考价值。

参考技术A

dubbo自带的运维工具dubbo-admin,主要面向开发人员去管理服务,携带很多管理、控制等功能,然后在dubbo新版本又推出了qos(Quality of Service),主要面向运维管理。我在之前公司有用到次功能,在和k8s结合时,通过http发送主动下线功能(下线注册,但不下线服务),等到流量完全停止,在下线pod,实现平滑发布。

怎么样去管理?

dubbo通过 QosProtocolWrapper 这个包装器实现qos发布,QosProtocolWrapper 是Protocol 三大包装器(filter,listener,qos)其中之一,默认会开启qos功能,可以配置关闭

qos 主要提供了ls,online,offline,help 功能,具体说,只有三种,上下线服务和查看服务

我们跟读一下源码,看看qos 服务的启动,请求处理,上下线等。

在dubbo 生产者服务暴露和消费者消费引用的过程中都会启动qos,并且qos 通过cas来保证一个jvm只启动一次。

同样qos 功能也是通过netty启动server,处理类指定为 QosProcessHandler,这个handler实现了netty的 ByteToMessageDecoder 可以将网络流中的字节解码为对象

channelActive() 方法含有为连接建立的时候回调,这里有个定时任务500ms,会刷一个美体的dubbo给客户端,我们验证下。

我们看看 QosProcessHandler#decode 是怎么处理请求的。

上面方法讲究,先读取第一个字节判断请求是http 还是 tcp,为什么用第一个字节呢,我们知道http信息头开始为 GET /xx 或者 POST /xx,第一个字符要么G要么P,判断为http 则用http编解码,如果为tcp 则用 LineBasedFrameDecoder 编解码,这是一个换行分割读取的解码方式遇到(\\n,\\r)[就是telnet时候的回车]时,就截断,如果为tcp 还会添加一个 IdleStateHandler 作为心跳检测,最后处理指令的handler 为 TelnetProcessHandler。

先演示下效果

为了便于观察我们这里看http的处理指令的方式 HttpProcessHandler。

HttpCommandDecoder. decode (msg)会将get或者post请求携带的路径等返回给 commandContext , BaseCommand.class 为指令扩展点会根据uri 传入的指令,来指定要处理的类,优点类似策略模式。我们看看offline 是怎么处理的

可以传入服务,默认所有服务,18行中从注册工厂中获取服务对应的注册中心,然后调用注册中心的unregister() 最后层层调用到zk客户端的delete()方法来,删除zk临时节点。

qos 的功能和简单,之所以单独拿出来讲是因为这里涵盖了我们web开发中常提到的“http服务器”概念,通过netty 启动服务,然后处理请求。

Dubbo Qos端口占用问题

前几天部署环境的时候,启动几个dubbo服务的时候,报了qos端口22222被占用。

貌似以前没遇到过这个错呢?

Qos是啥?

qos是dubbo的在线运维命令。

dubbo2.5.8新版本重构了telnet模块,提供了新的telnet命令支持,新版本的telnet端口与dubbo协议的端口是不同的端口,默认为22222。可以对dubbo服务上/下线。

qos使用示例参考

QoS参数配置

可以通过如下方式进行配置

  • JVM系统属性
  • dubbo.properties(在项目的src/main/resources目录下添加dubbo.properties文件)
  • XML方式
  • Spring-boot自动装配方式

其中,上述方式的优先顺序为 JVM系统属性 > dubbo.properties > XML/Spring-boot自动装配方式。

dubbo 2.5.9 版本

如果要修改qos.port,可以通过如下方式达到目的:【类QosProtocolWrapper】

方法一:在spring-dubbo.xml中配置如下

<dubbo:application name="${project.name}">
    <!-- 关闭qos -->
    <dubbo:parameter key="qos.enable" value="false"/>
    <dubbo:parameter key="qos.port" value="22223"/>
</dubbo:application>

方法二:配置启动参数

-Ddubbo.application.qos.port=33333 -Ddubbo.application.qos.enable=false

dubbo 2.6.0版本

取qos.port 是 【-Ddubbo.qos.port=33333】, 且无法通过配置关闭。

在com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper中有启动qos

public class ProtocolListenerWrapper implements Protocol {
    static {
        try {
            Class serverClass = Protocol.class.getClassLoader().loadClass("com.alibaba.dubbo.qos.server.Server");
            Method serverGetInstanceMethod = serverClass.getMethod("getInstance");
            Object serverInstance = serverGetInstanceMethod.invoke(null);
            Method startMethod = serverClass.getMethod("start");
            // 这里没有判断配置,直接反射调用start了
            startMethod.invoke(serverInstance);
        }catch (Throwable throwable){

        }
    }
    // ...
}

不过可以在qos-server启动之后,再关掉它。

// 关闭dubbo qos service
com.alibaba.dubbo.qos.server.Server.getInstance().stop();

在com.alibaba.dubbo.qos.server.Server中,有如下代码段:

public class Server {
    private static final Logger logger = LoggerFactory.getLogger(Server.class);
    private static final Server INSTANCE = new Server();

    public static final Server getInstance() {
        return INSTANCE;
    }
	// 这里直接取端口号
    private int port = Integer.parseInt(ConfigUtils.getProperty(Constants.QOS_PORT, Constants.DEFAULT_PORT + ""));

    // ...略部分代码...
    
   /**
     * start server, bind port
     */
    public void start() throws Throwable {
        if (!hasStarted.compareAndSet(false, true)) {
            return;
        }
        boss = new NioEventLoopGroup(0, new DefaultThreadFactory("qos-boss", true));
        worker = new NioEventLoopGroup(0, new DefaultThreadFactory("qos-worker", true));
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boss, worker);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
        serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new QosProcessHandler(welcome));
            }
        });
        try {
            serverBootstrap.bind(port).sync();
            logger.info("qos-server bind localhost:" + port);
        } catch (Throwable throwable) {
            // 这里会打印端口冲突错误
            logger.error("qos-server can not bind localhost:" + port, throwable);
            throw throwable;
        }
    }
}

为什么以前没遇到22222端口占用问题

之前依赖的是dubbo-2.5.9。在这个版本的QosProtocolWrapper中,可以看到:

private void startQosServer(URL url) {
        if (!hasStarted.compareAndSet(false, true)) {
            return;
        }

        try {
            boolean qosEnable = Boolean.parseBoolean(url.getParameter(QOS_ENABLE,"true"));
            if (!qosEnable) {
                return;
            }

            int port = Integer.parseInt(url.getParameter(QOS_PORT,"22222"));
            boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP,"true"));
            // 如果没有netty4,下面这行代码就会报错
            Server server = com.alibaba.dubbo.qos.server.Server.getInstance();
            server.setPort(port);
            server.setAcceptForeignIp(acceptForeignIp);
            server.start();
        } catch (Throwable throwable) {
            // 这里错误直接吃掉了,没往外部抛出来
            //throw new RpcException("fail to start qos server", throwable);
        }
    }

qos-server 需要netty4版本的支持,默认情况下dubbo不会引用netty4的依赖包。

而最近新加的功能确实间接引入了netty4。

关闭qos

对于2.5.9的版本,可以通过如下方式关闭qos

<dubbo:parameter key="qos.enable" value="false"/>

或者

-Ddubbo.application.qos.enable=false

对于2.6.0的版本,没办法通过配置关闭,只能是Server有stop方法了

以上是关于dubbo系列之-qos运维-2021-01-17的主要内容,如果未能解决你的问题,请参考以下文章

WebRTC系列分享 第二期 | WebRTC QoS方法之Pacer实现

WebRTC系列-Qos系列之音频设置丢包重传nack

5GC基础自学系列 | N4口规则详解之QER(Qos执行规则)

Dubbo系列之Dubbo 服务暴露

SpringBoot系列之集成Dubbo的方式

初识Dubbo 系列之6-Dubbo 配置