/* * Copyright 2009-2016 Weibo, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.weibo.api.motan.cluster.support; import com.weibo.api.motan.cluster.Cluster; import com.weibo.api.motan.cluster.HaStrategy; import com.weibo.api.motan.cluster.LoadBalance; import com.weibo.api.motan.common.MotanConstants; import com.weibo.api.motan.common.URLParamType; import com.weibo.api.motan.core.extension.ExtensionLoader; import com.weibo.api.motan.exception.MotanErrorMsgConstant; import com.weibo.api.motan.exception.MotanFrameworkException; import com.weibo.api.motan.protocol.support.ProtocolFilterDecorator; import com.weibo.api.motan.registry.NotifyListener; import com.weibo.api.motan.registry.Registry; import com.weibo.api.motan.registry.RegistryFactory; import com.weibo.api.motan.rpc.Protocol; import com.weibo.api.motan.rpc.Referer; import com.weibo.api.motan.rpc.URL; import com.weibo.api.motan.util.CollectionUtil; import com.weibo.api.motan.util.LoggerUtil; import com.weibo.api.motan.util.StringTools; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * Notify cluster the referers have changed. * * @author fishermen * @version V1.0 created at: 2013-5-31 */ public class ClusterSupport<T> implements NotifyListener { private static ConcurrentHashMap<String, Protocol> protocols = new ConcurrentHashMap<String, Protocol>(); private Cluster<T> cluster; private List<URL> registryUrls; private URL url; private Class<T> interfaceClass; private Protocol protocol; private ConcurrentHashMap<URL, List<Referer<T>>> registryReferers = new ConcurrentHashMap<URL, List<Referer<T>>>(); public ClusterSupport(Class<T> interfaceClass, List<URL> registryUrls) { this.registryUrls = registryUrls; this.interfaceClass = interfaceClass; String urlStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName())); this.url = URL.valueOf(urlStr); protocol = getDecorateProtocol(url.getProtocol()); } public void init() { prepareCluster(); URL subUrl = toSubscribeUrl(url); for (URL ru : registryUrls) { String directUrlStr = ru.getParameter(URLParamType.directUrl.getName()); // 如果有directUrl,直接使用这些directUrls进行初始化,不用到注册中心discover if (StringUtils.isNotBlank(directUrlStr)) { List<URL> directUrls = parseDirectUrls(directUrlStr); if (!directUrls.isEmpty()) { notify(ru, directUrls); LoggerUtil.info("Use direct urls, refUrl={}, directUrls={}", url, directUrls); continue; } } // client 注册自己,同时订阅service列表 Registry registry = getRegistry(ru); registry.subscribe(subUrl, this); } boolean check = Boolean.parseBoolean(url.getParameter(URLParamType.check.getName(), URLParamType.check.getValue())); if (!CollectionUtil.isEmpty(cluster.getReferers()) || !check) { cluster.init(); if (CollectionUtil.isEmpty(cluster.getReferers()) && !check) { LoggerUtil.warn(String.format("refer:%s", this.url.getPath() + "/" + this.url.getVersion()), "No services"); } return; } throw new MotanFrameworkException(String.format("ClusterSupport No service urls for the refer:%s, registries:%s", this.url.getIdentity(), registryUrls), MotanErrorMsgConstant.SERVICE_UNFOUND); } public void destroy() { URL subscribeUrl = toSubscribeUrl(url); for (URL ru : registryUrls) { try { Registry registry = getRegistry(ru); registry.unsubscribe(subscribeUrl, this); if (!MotanConstants.NODE_TYPE_REFERER.equals(url.getParameter(URLParamType.nodeType.getName()))) { registry.unregister(url); } } catch (Exception e) { LoggerUtil.warn(String.format("Unregister or unsubscribe false for url (%s), registry= %s", url, ru.getIdentity()), e); } } try { getCluster().destroy(); } catch (Exception e) { LoggerUtil.warn(String.format("Exception when destroy cluster: %s", getCluster().getUrl())); } } protected Registry getRegistry(URL url) { RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol()); return registryFactory.getRegistry(url); } private URL toSubscribeUrl(URL url) { URL subUrl = url.createCopy(); subUrl.addParameter(URLParamType.nodeType.getName(), MotanConstants.NODE_TYPE_SERVICE); return subUrl; } /** * <pre> * 1 notify的执行需要串行 * 2 notify通知都是全量通知,在设入新的referer后,cluster需要把不再使用的referer进行回收,避免资源泄漏; * 3 如果该registry对应的referer数量为0,而没有其他可用的referers,那就忽略该次通知; * 4 此处对protoco进行decorator处理,当前为增加filters * </pre> */ @Override public synchronized void notify(URL registryUrl, List<URL> urls) { if (CollectionUtil.isEmpty(urls)) { onRegistryEmpty(registryUrl); LoggerUtil.warn("ClusterSupport config change notify, urls is empty: registry={} service={} urls=[]", registryUrl.getUri(), url.getIdentity()); return; } LoggerUtil.info("ClusterSupport config change notify: registry={} service={} urls={}", registryUrl.getUri(), url.getIdentity(), getIdentities(urls)); // 通知都是全量通知,在设入新的referer后,cluster内部需要把不再使用的referer进行回收,避免资源泄漏 // //////////////////////////////////////////////////////////////////////////////// // 判断urls中是否包含权重信息,并通知loadbalance。 processWeights(urls); List<Referer<T>> newReferers = new ArrayList<Referer<T>>(); for (URL u : urls) { if (!u.canServe(url)) { continue; } Referer<T> referer = getExistingReferer(u, registryReferers.get(registryUrl)); if (referer == null) { // careful u: serverURL, refererURL的配置会被serverURL的配置覆盖 URL refererURL = u.createCopy(); mergeClientConfigs(refererURL); referer = protocol.refer(interfaceClass, refererURL, u); } if (referer != null) { newReferers.add(referer); } } if (CollectionUtil.isEmpty(newReferers)) { onRegistryEmpty(registryUrl); return; } // 此处不销毁referers,由cluster进行销毁 registryReferers.put(registryUrl, newReferers); refreshCluster(); } /** * 检查urls中的第一个url是否为权重信息。 如果是权重信息则把权重信息传递给loadbalance,并移除权重url。 * * @param urls */ private void processWeights(List<URL> urls) { if (urls != null && !urls.isEmpty()) { URL ruleUrl = urls.get(0); // 没有权重时需要传递默认值。因为可能是变更时去掉了权重 String weights = URLParamType.weights.getValue(); if ("rule".equalsIgnoreCase(ruleUrl.getProtocol())) { weights = ruleUrl.getParameter(URLParamType.weights.getName(), URLParamType.weights.getValue()); urls.remove(0); } LoggerUtil.info("refresh weight. weight=" + weights); this.cluster.getLoadBalance().setWeightString(weights); } } private void onRegistryEmpty(URL excludeRegistryUrl) { boolean noMoreOtherRefers = registryReferers.size() == 1 && registryReferers.containsKey(excludeRegistryUrl); if (noMoreOtherRefers) { LoggerUtil.warn(String.format("Ignore notify for no more referers in this cluster, registry: %s, cluster=%s", excludeRegistryUrl, getUrl())); } else { registryReferers.remove(excludeRegistryUrl); refreshCluster(); } } protected Protocol getDecorateProtocol(String protocolName) { Protocol decorateProtocol = protocols.get(protocolName); if (decorateProtocol == null) { protocols.putIfAbsent(protocolName, new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class) .getExtension(protocolName))); decorateProtocol = protocols.get(protocolName); } return decorateProtocol; } private Referer<T> getExistingReferer(URL url, List<Referer<T>> referers) { if (referers == null) { return null; } for (Referer<T> r : referers) { if (ObjectUtils.equals(url, r.getUrl()) || ObjectUtils.equals(url, r.getServiceUrl())) { return r; } } return null; } /** * refererURL的扩展参数中,除了application、module外,其他参数被client覆盖, 如果client没有则使用referer的参数 * * @param refererURL */ private void mergeClientConfigs(URL refererURL) { String application = refererURL.getParameter(URLParamType.application.getName(), URLParamType.application.getValue()); String module = refererURL.getParameter(URLParamType.module.getName(), URLParamType.module.getValue()); refererURL.addParameters(this.url.getParameters()); refererURL.addParameter(URLParamType.application.getName(), application); refererURL.addParameter(URLParamType.module.getName(), module); } private void refreshCluster() { List<Referer<T>> referers = new ArrayList<Referer<T>>(); for (List<Referer<T>> refs : registryReferers.values()) { referers.addAll(refs); } cluster.onRefresh(referers); } public Cluster<T> getCluster() { return cluster; } public URL getUrl() { return url; } private String getIdentities(List<URL> urls) { if (urls == null || urls.isEmpty()) { return "[]"; } StringBuilder builder = new StringBuilder(); builder.append("["); for (URL u : urls) { builder.append(u.getIdentity()).append(","); } builder.setLength(builder.length() - 1); builder.append("]"); return builder.toString(); } @SuppressWarnings("unchecked") private void prepareCluster() { String clusterName = url.getParameter(URLParamType.cluster.getName(), URLParamType.cluster.getValue()); String loadbalanceName = url.getParameter(URLParamType.loadbalance.getName(), URLParamType.loadbalance.getValue()); String haStrategyName = url.getParameter(URLParamType.haStrategy.getName(), URLParamType.haStrategy.getValue()); cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(clusterName); LoadBalance<T> loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName); HaStrategy<T> ha = ExtensionLoader.getExtensionLoader(HaStrategy.class).getExtension(haStrategyName); cluster.setLoadBalance(loadBalance); cluster.setHaStrategy(ha); cluster.setUrl(url); } private List<URL> parseDirectUrls(String directUrlStr) { String[] durlArr = MotanConstants.COMMA_SPLIT_PATTERN.split(directUrlStr); List<URL> directUrls = new ArrayList<URL>(); for (String dus : durlArr) { URL du = URL.valueOf(StringTools.urlDecode(dus)); if (du != null) { directUrls.add(du); } } return directUrls; } }