/* * 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.registry.support.command; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import com.weibo.api.motan.common.URLParamType; import com.weibo.api.motan.exception.MotanFrameworkException; import com.weibo.api.motan.registry.NotifyListener; import com.weibo.api.motan.rpc.URL; import com.weibo.api.motan.util.CollectionUtil; import com.weibo.api.motan.util.ConcurrentHashSet; import com.weibo.api.motan.util.LoggerUtil; import com.weibo.api.motan.util.MotanSwitcherUtil; import com.weibo.api.motan.util.NetUtils; public class CommandServiceManager implements CommandListener, ServiceListener { public static final String MOTAN_COMMAND_SWITCHER = "feature.motanrpc.command.enable"; private static Pattern IP_PATTERN = Pattern.compile("^!?[0-9.]*\\*?$"); static { MotanSwitcherUtil.initSwitcher(MOTAN_COMMAND_SWITCHER, true); } private URL refUrl; private ConcurrentHashSet<NotifyListener> notifySet; private CommandFailbackRegistry registry; // service cache private Map<String, List<URL>> groupServiceCache; // command cache private String commandStringCache = ""; private volatile RpcCommand commandCache; public CommandServiceManager(URL refUrl) { LoggerUtil.info("CommandServiceManager init url:" + refUrl.toFullStr()); this.refUrl = refUrl; notifySet = new ConcurrentHashSet<NotifyListener>(); groupServiceCache = new ConcurrentHashMap<String, List<URL>>(); } @Override public void notifyService(URL serviceUrl, URL registryUrl, List<URL> urls) { if (registry == null) { throw new MotanFrameworkException("registry must be set."); } URL urlCopy = serviceUrl.createCopy(); String groupName = urlCopy.getParameter(URLParamType.group.getName(), URLParamType.group.getValue()); groupServiceCache.put(groupName, urls); List<URL> finalResult = new ArrayList<URL>(); if (commandCache != null) { Map<String, Integer> weights = new HashMap<String, Integer>(); finalResult = discoverServiceWithCommand(refUrl, weights, commandCache); } else { LoggerUtil.info("command cache is null. service:" + serviceUrl.toSimpleString()); // 没有命令时,只返回这个manager实际group对应的结果 finalResult.addAll(discoverOneGroup(refUrl)); } for (NotifyListener notifyListener : notifySet) { notifyListener.notify(registry.getUrl(), finalResult); } } @Override public void notifyCommand(URL serviceUrl, String commandString) { LoggerUtil.info("CommandServiceManager notify command. service:" + serviceUrl.toSimpleString() + ", command:" + commandString); if (!MotanSwitcherUtil.isOpen(MOTAN_COMMAND_SWITCHER) || commandString == null) { LoggerUtil.info("command reset empty since swither is close."); commandString = ""; } List<URL> finalResult = new ArrayList<URL>(); URL urlCopy = serviceUrl.createCopy(); if (!StringUtils.equals(commandString, commandStringCache)) { commandStringCache = commandString; commandCache = RpcCommandUtil.stringToCommand(commandStringCache); Map<String, Integer> weights = new HashMap<String, Integer>(); if (commandCache != null) { commandCache.sort(); finalResult = discoverServiceWithCommand(refUrl, weights, commandCache); } else { // 如果是指令有异常时,应当按没有指令处理,防止错误指令导致服务异常 if (StringUtils.isNotBlank(commandString)) { LoggerUtil.warn("command parse fail, ignored! command:" + commandString); commandString = ""; } // 没有命令时,只返回这个manager实际group对应的结果 finalResult.addAll(discoverOneGroup(refUrl)); } // 指令变化时,删除不再有效的缓存,取消订阅不再有效的group Set<String> groupKeys = groupServiceCache.keySet(); for (String gk : groupKeys) { if (!weights.containsKey(gk)) { groupServiceCache.remove(gk); URL urlTemp = urlCopy.createCopy(); urlTemp.addParameter(URLParamType.group.getName(), gk); registry.unsubscribeService(urlTemp, this); } } } else { LoggerUtil.info("command not change. url:" + serviceUrl.toSimpleString()); // 指令没有变化,什么也不做 return; } for (NotifyListener notifyListener : notifySet) { notifyListener.notify(registry.getUrl(), finalResult); } // 当指令从有改到无时,会触发取消订阅所有的group,需要重新订阅本组的service if ("".equals(commandString)) { LoggerUtil.info("reSub service" + refUrl.toSimpleString()); registry.subscribeService(refUrl, this); } } public List<URL> discoverServiceWithCommand(URL serviceUrl, Map<String, Integer> weights, RpcCommand rpcCommand) { String localIP = NetUtils.getLocalAddress().getHostAddress(); return this.discoverServiceWithCommand(serviceUrl, weights, rpcCommand, localIP); } public List<URL> discoverServiceWithCommand(URL serviceUrl, Map<String, Integer> weights, RpcCommand rpcCommand, String localIP) { if (rpcCommand == null || CollectionUtil.isEmpty(rpcCommand.getClientCommandList())) { return discoverOneGroup(serviceUrl); } List<URL> mergedResult = new LinkedList<URL>(); String path = serviceUrl.getPath(); List<RpcCommand.ClientCommand> clientCommandList = rpcCommand.getClientCommandList(); boolean hit = false; for (RpcCommand.ClientCommand command : clientCommandList) { mergedResult = new LinkedList<URL>(); // 判断当前url是否符合过滤条件 boolean match = RpcCommandUtil.match(command.getPattern(), path); if (match) { hit = true; if (!CollectionUtil.isEmpty(command.getMergeGroups())) { // 计算出所有要合并的分组及权重 try { buildWeightsMap(weights, command); } catch (MotanFrameworkException e) { LoggerUtil.warn("build weights map fail!" + e.getMessage()); continue; } // 根据计算结果,分别发现各个group的service,合并结果 mergedResult.addAll(mergeResult(serviceUrl, weights)); } else { mergedResult.addAll(discoverOneGroup(serviceUrl)); } LoggerUtil.info("mergedResult: size-" + mergedResult.size() + " --- " + mergedResult.toString()); if (!CollectionUtil.isEmpty(command.getRouteRules())) { LoggerUtil.info("router: " + command.getRouteRules().toString()); for (String routeRule : command.getRouteRules()) { String[] fromTo = routeRule.replaceAll("\\s+", "").split("to"); if (fromTo.length != 2) { routeRuleConfigError(); continue; } String from = fromTo[0]; String to = fromTo[1]; if (from.length() < 1 || to.length() < 1 || !IP_PATTERN.matcher(from).find() || !IP_PATTERN.matcher(to).find()) { routeRuleConfigError(); continue; } boolean oppositeFrom = from.startsWith("!"); boolean oppositeTo = to.startsWith("!"); if (oppositeFrom) { from = from.substring(1); } if (oppositeTo) { to = to.substring(1); } int idx = from.indexOf('*'); boolean matchFrom; if (idx != -1) { matchFrom = localIP.startsWith(from.substring(0, idx)); } else { matchFrom = localIP.equals(from); } // 开头有!,取反 if (oppositeFrom) { matchFrom = !matchFrom; } LoggerUtil.info("matchFrom: " + matchFrom + ", localip:" + localIP + ", from:" + from); if (matchFrom) { boolean matchTo; Iterator<URL> iterator = mergedResult.iterator(); while (iterator.hasNext()) { URL url = iterator.next(); if (url.getProtocol().equalsIgnoreCase("rule")) { continue; } idx = to.indexOf('*'); if (idx != -1) { matchTo = url.getHost().startsWith(to.substring(0, idx)); } else { matchTo = url.getHost().equals(to); } if (oppositeTo) { matchTo = !matchTo; } if (!matchTo) { iterator.remove(); LoggerUtil.info("router To not match. url remove : " + url.toSimpleString()); } } } } } // 只取第一个匹配的 TODO 考虑是否能满足绝大多数场景需求 break; } } List<URL> finalResult = new ArrayList<URL>(); if (!hit) { finalResult = discoverOneGroup(serviceUrl); } else { finalResult.addAll(mergedResult); } return finalResult; } private void buildWeightsMap(Map<String, Integer> weights, RpcCommand.ClientCommand command) { for (String rule : command.getMergeGroups()) { String[] gw = rule.split(":"); int weight = 1; if (gw.length > 1) { try { weight = Integer.parseInt(gw[1]); } catch (NumberFormatException e) { weightConfigError(); } if (weight < 0 || weight > 100) { weightConfigError(); } } weights.put(gw[0], weight); } } private List<URL> mergeResult(URL url, Map<String, Integer> weights) { List<URL> finalResult = new ArrayList<URL>(); if (weights.size() > 1) { // 将所有group及权重拼接成一个rule的URL,并作为第一个元素添加到最终结果中 URL ruleUrl = new URL("rule", url.getHost(), url.getPort(), url.getPath()); StringBuilder weightsBuilder = new StringBuilder(64); for (Map.Entry<String, Integer> entry : weights.entrySet()) { weightsBuilder.append(entry.getKey()).append(':').append(entry.getValue()).append(','); } ruleUrl.addParameter(URLParamType.weights.getName(), weightsBuilder.deleteCharAt(weightsBuilder.length() - 1).toString()); finalResult.add(ruleUrl); } for (String key : weights.keySet()) { if (groupServiceCache.containsKey(key)) { finalResult.addAll(groupServiceCache.get(key)); } else { URL urlTemp = url.createCopy(); urlTemp.addParameter(URLParamType.group.getName(), key); finalResult.addAll(discoverOneGroup(urlTemp)); registry.subscribeService(urlTemp, this); } } return finalResult; } private List<URL> discoverOneGroup(URL urlCopy) { LoggerUtil.info("CommandServiceManager discover one group. url:" + urlCopy.toSimpleString()); String group = urlCopy.getParameter(URLParamType.group.getName(), URLParamType.group.getValue()); List<URL> list = groupServiceCache.get(group); if (list == null) { list = registry.discoverService(urlCopy); groupServiceCache.put(group, list); } return list; } public void setCommandCache(String command) { commandStringCache = command; commandCache = RpcCommandUtil.stringToCommand(commandStringCache); LoggerUtil.info("CommandServiceManager set commandcache. commandstring:" + commandStringCache + ", comandcache " + (commandCache == null ? "is null." : "is not null.")); } public void addNotifyListener(NotifyListener notifyListener) { notifySet.add(notifyListener); } public void removeNotifyListener(NotifyListener notifyListener) { notifySet.remove(notifyListener); } public void setRegistry(CommandFailbackRegistry registry) { this.registry = registry; } private void weightConfigError() { throw new MotanFrameworkException("权重比只能是[0,100]间的整数"); } private void routeRuleConfigError() { LoggerUtil.warn("路由规则配置不合法"); } }