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(); } }