cometd源码阅读-oort集群初始化(十三)

说明

官方网文档:https://docs.cometd.org/current5/reference/#_java_oort

原理是当前客户端伪装成正常客户端,去订阅oort.cloud服务 对应的渠道,然后就能正常收到消息 再转发给当前节点,OORT相当于是一个中转站,将订阅的其他节点指定渠道的消息转发给当前节点

这里我们主要看OortStaticConfigServlet静态初始化

需要注意的问题

comtd自己内置了一套协议如握手续约,客户端使用websocket 不会有啥问题,因为websocket是一致建立了连接 连接成功,后续握手 续约等协议都是在一台机器上

但是针对http轮训,会出现问题 在A机器握手成功 下一次续约或者订阅渠道到了B机器.

针对这种情况需要nginx配置规则 比如ip_hash  或者自定义策略 握手成功 后续仅内置协议到握手成功那一台

 

 

基本配置

 

 

 

server1

  <servlet>
    <servlet-name>oort</servlet-name>
    <servlet-class>org.cometd.oort.OortStaticConfigServlet</servlet-class>
    <!--当前节点服务的ip端口,用于测试当前节点是否正常-->
    <init-param>
      <param-name>oort.url</param-name>
      <param-value>http://localhost:8081/cometd-demo-5.0.13-SNAPSHOT/cometd</param-value>
    </init-param>
    <!--集群其他节点,会生成对应的客户端与以下集群节点进行连接-->
    <init-param>
      <param-name>oort.cloud</param-name>
      <param-value>http://localhost:8082/cometd-demo-5.0.13-SNAPSHOT/cometd</param-value>
    </init-param>

<!--上面的oort.cloud建立连接后会订阅以下指定渠道--> <init-param> <param-name>oort.channels</param-name> <param-value>/chat/*,/members/*</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet>

server2

  <servlet>
    <servlet-name>oort</servlet-name>
    <servlet-class>org.cometd.oort.OortStaticConfigServlet</servlet-class>
    <init-param>
      <param-name>oort.url</param-name>
      <param-value>http://localhost:8082/cometd-demo-5.0.13-SNAPSHOT/cometd</param-value>
    </init-param>
    <init-param>
      <param-name>oort.cloud</param-name>
      <param-value>http://localhost:8081/cometd-demo-5.0.13-SNAPSHOT/cometd</param-value>
    </init-param>
    <init-param>
      <param-name>oort.channels</param-name>
      <param-value>/chat/*,/members/*</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
  </servlet>

<1>

org.cometd.oort.OortConfigServlet#init

  @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        ServletContext servletContext = config.getServletContext();
        //获取当前服务的bayuxServer
        BayeuxServer bayeux = (BayeuxServer)servletContext.getAttribute(BayeuxServer.ATTRIBUTE);
        if (bayeux == null) {
            throw new UnavailableException("Missing " + BayeuxServer.ATTRIBUTE + " attribute");
        }

        //获取当前节点的urlgetServletConfig().getInitParameter(OORT_URL_PARAM);
        String url = provideOortURL();
        if (url == null) {
            throw new UnavailableException("Missing " + OORT_URL_PARAM + " init parameter");
        }

        try {
            //初始化oort客户端保存bayeux 以及当前节点的地址
            Oort oort = newOort(bayeux, url);
            //<2>配置ServletParameter的自定义配置
            configureOort(config, oort);

            //oort初始化在当前BayeuxServer 添加初始化oort相关channel以及session
            //<3>内部调用org.cometd.oort.Oort#doStart
            oort.start();
            servletContext.setAttribute(Oort.OORT_ATTRIBUTE, oort);
            //<5>将节点加入集群的相关初始化
            new Thread(new Starter(config, oort)).start();
        } catch (Exception x) {
            throw new ServletException(x);
        }
    }

<2>

org.cometd.oort.OortConfigServlet#configureOort

protected void configureOort(ServletConfig config, Oort oort) throws Exception {
        String secret = config.getInitParameter(OORT_SECRET_PARAM);
        if (secret != null) {
            oort.setSecret(secret);
        }

        String enableAckExtension = config.getInitParameter(OORT_ENABLE_ACK_EXTENSION_PARAM);
        if (enableAckExtension == null) {
            enableAckExtension = "true";
        }
        oort.setAckExtensionEnabled(Boolean.parseBoolean(enableAckExtension));

        String enableBinaryExtension = config.getInitParameter(OORT_ENABLE_BINARY_EXTENSION_PARAM);
        if (enableBinaryExtension == null) {
            enableBinaryExtension = "true";
        }
        oort.setBinaryExtensionEnabled(Boolean.parseBoolean(enableBinaryExtension));

        String jsonContext = config.getInitParameter(OORT_JSON_CONTEXT_PARAM);
        if (jsonContext != null) {
            Class<?> klass = getClass().getClassLoader().loadClass(jsonContext);
            oort.setJSONContextClient((JSONContext.Client)klass.getConstructor().newInstance());
        }

        //ClientTransport的Factory
        String clientTransportFactories = config.getInitParameter(OORT_CLIENT_TRANSPORT_FACTORIES_PARAM);
        if (clientTransportFactories != null) {
            List<ClientTransport.Factory> factories = new ArrayList<>();
            for (String className : clientTransportFactories.split(",")) {
                Class<?> klass = getClass().getClassLoader().loadClass(className.trim());
                factories.add((ClientTransport.Factory)klass.getConstructor().newInstance());
            }
            oort.setClientTransportFactories(factories);
        }
    }

<3>

org.cometd.oort.Oort#doStart

    @Override
    protected void doStart() throws Exception {
        ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1);
        scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        scheduler.setRemoveOnCancelPolicy(true);
        _scheduler = scheduler;

        //如果没有指定 默认支持websoket和http
        if (_transportFactories.isEmpty()) {
            _transportFactories.add(new WebSocketTransport.Factory());
            _transportFactories.add(new JettyHttpClientTransport.Factory(new HttpClient()));
        }
        for (ClientTransport.Factory factory : _transportFactories) {
            addBean(factory);
        }

        if (isAckExtensionEnabled()) {
            boolean present = false;
            for (Extension extension : _bayeux.getExtensions()) {
                //是否有定义extension扩展点
                if (extension instanceof AcknowledgedMessagesExtension) {
                    present = true;
                    break;
                }
            }
            //没定义设置默认
            if (!present) {
                _bayeux.addExtension(_ackExtension = new AcknowledgedMessagesExtension());
            }
        }


        if (isBinaryExtensionEnabled()) {
            _oortSession.addExtension(_binaryExtension = new org.cometd.client.ext.BinaryExtension());
            boolean present = false;
            // 是否有定义extension扩展点
            for (Extension extension : _bayeux.getExtensions()) {
                if (extension instanceof BinaryExtension) {
                    present = true;
                    break;
                }
            }
            //没定义设置默认
            if (!present) {
                _bayeux.addExtension(_serverBinaryExtension = new BinaryExtension());
            }
        }


        //新增一个监听器订阅AllChannelsFilter 内部会给订阅的session新增一个message过滤器
        _bayeux.addListener(_allChannelsFilter);

        //创建oort cloud channel OORT_CLOUD_CHANNEL = "/oort/cloud";
        ServerChannel oortCloudChannel = _bayeux.createChannelIfAbsent(OORT_CLOUD_CHANNEL).getReference();
        //cloud添加监听器
        oortCloudChannel.addListener(_cloudListener);

        //oortSession是通过 bayeux.newLocalSession("oort");初始化的 是在当前节点创建了一个session
        _oortSession.handshake();

        //<4>给oort相关channel添加授权
        protectOortChannels(_bayeux);

        super.doStart();
    }

<4>

 protected void protectOortChannels(BayeuxServer bayeux) {
        PROTECTED_CHANNELS.forEach(name -> bayeux.createChannelIfAbsent(name, channel -> channel.addAuthorizer(_authorizer)));
    }

<5>

org.cometd.oort.OortConfigServlet.Starter#run

 public void run() {
            // Connect to myself until success. If the handshake fails,
            // the normal BayeuxClient retry mechanism will kick-in.
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Connecting to self: {}", oort);
            }
            //连接当前节点 如果连接失败则会自动重试 感觉应该是测试是否可以连通
            Map<String, Object> fields = oort.newOortHandshakeFields(oort.getURL(), null);
            //伪造一个节点 与当前节点建立连接 用于测试当前节点是否能正常接收请求
            oortComet.handshake(fields, message -> {
                // If the handshake fails but has an advice field, it means it
                // reached the server but was denied e.g. by a SecurityPolicy.
                Map<String, Object> advice = message.getAdvice();
                //握手成功过
                if (message.isSuccessful() || advice != null) {
                     if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Connected to self: {}", oort);
                    }
                    //测试连接的链接  断开连接
                    oortComet.disconnect();
                     //<7>表示oort client可以跟当前节点建立连接 构建oort.cloud集群
                    joinCloud();
                }
            });
        }

<7>

org.cometd.oort.OortConfigServlet.Starter#joinCloud

    private void joinCloud() {
            try {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Joining cloud: {}", oort);
                }

                //<9>子类实现 将其他节点加入集群
                configureCloud(config, oort);

                //订阅集群节点的渠道
                String channels = config.getInitParameter(OORT_CHANNELS_PARAM);
                if (channels != null) {
                    String[] patterns = channels.split(",");
                    for (String channel : patterns) {
                        channel = channel.trim();
                        if (channel.length() > 0) {
                            oort.observeChannel(channel);
                        }
                    }
                }
            } catch (Throwable x) {
                LOGGER.warn("Could not start Oort", x);
                destroy();
            }
        }

<9>

org.cometd.oort.OortStaticConfigServlet#configureCloud

   @Override
    protected void configureCloud(ServletConfig config, Oort oort) throws Exception {
        //获取oort.cloud配置
        String cloud = config.getInitParameter(OORT_CLOUD_PARAM);
        if (cloud != null && cloud.length() > 0) {
            //,分割
            String[] urls = cloud.split(",");
            for (String comet : urls) {
                comet = comet.trim();
                if (comet.length() > 0) {
                    //<10>调用oort.observeComet 加入集群 用此方法我们也可以手动加入集群
                    OortComet oortComet = oort.observeComet(comet);
                    if (oortComet == null) {
                        throw new IllegalArgumentException("Invalid value for " + OORT_CLOUD_PARAM);
                    }
                }
            }
        }
    }

<10>

org.cometd.oort.Oort#observeComet

public OortComet observeComet(String cometURL) {
        //<11>_membership oort对象 是集群成员管理器 内部维护了建立连接的就请你和未建立连接的
        return _membership.observeComet(cometURL);
    }

<11>

org.cometd.oort.OortMembership#observeComet

    private OortComet observeComet(String cometURL, String oortAliasURL) {
        try {
            URI uri = new URI(cometURL);
            if (uri.getScheme() == null) {
                throw new IllegalArgumentException("Missing protocol in comet URL " + cometURL);
            }
            if (uri.getHost() == null) {
                throw new IllegalArgumentException("Missing host in comet URL " + cometURL);
            }
        } catch (URISyntaxException x) {
            throw new IllegalArgumentException(x);
        }

        if (oort.getURL().equals(cometURL)) {
            return null;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Observing comet {}", cometURL);
        }

        OortComet oortComet;
        synchronized (lock) {
            //先尝试从已建立连接获取(防止重复加入 )
            oortComet = oort.getComet(cometURL);
            if (oortComet != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Comet {} is already connected with {}", cometURL, oortComet);
                }
                return oortComet;
            }

            //如果上面已建立没有 尝试从从正在建立连接 或者失败重试的获取 握手成功将会移除HandshakeListener (防止重复加入 )
            oortComet = pendingComets.get(cometURL);
            if (oortComet != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Comet {} is already connecting with {}", cometURL, oortComet);
                }
                return oortComet;
            }

            //表示未创建 创建一个oortComet
            oortComet = createOortComet(cometURL);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Connecting to comet {} with {}", cometURL, oortComet);
        }

        Map<String, Object> fields = oort.newOortHandshakeFields(cometURL, oortAliasURL);
        //<12>建立连接
        oort.connectComet(oortComet, fields);
        return oortComet;
    }

<12>

具体通信都在Transport

org.cometd.client.BayeuxClient#handshake

 private void handshaking(Map<String, Object> handshakeFields, ClientSession.MessageListener handshakeCallback) {
            if (update(State.HANDSHAKING)) {
                initialize();

                List<String> allowedTransports = getAllowedTransports();
                // Pick the first transport for the handshake, it will renegotiate if not right
                //选择传输处理器 webSocket还是Http轮训
                ClientTransport transport = transportRegistry.negotiate(allowedTransports.toArray(), BAYEUX_VERSION).get(0);
                prepareTransport(null, transport);
                if (logger.isDebugEnabled()) {
                    logger.debug("Using initial transport {} from {}", transport.getName(), allowedTransports);
                }

                synchronized (this) {
                    this.transport = transport;
                    this.handshakeFields = handshakeFields;
                    this.handshakeCallback = handshakeCallback;
                }

                resetSubscriptions();
                sendHandshake();
            }
        }

 

posted @ 2022-10-23 15:01  意犹未尽  阅读(94)  评论(0编辑  收藏  举报