All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.nacos.naming.selector.LabelSelector Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * 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.alibaba.nacos.naming.selector;


import com.alibaba.nacos.api.cmdb.pojo.PreservedEntityTypes;
import com.alibaba.nacos.api.selector.ExpressionSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.cmdb.service.CmdbReader;
import com.alibaba.nacos.naming.boot.SpringContext;
import com.alibaba.nacos.naming.core.Instance;
import com.alibaba.nacos.api.exception.NacosException;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A selector to implement a so called same-label-prior rule for service discovery.
 * 

Backgroup

* Consider service providers are deployed in two sites i.e. site A and site B, and consumers * of this service provider are also deployed in site A and site B. So the consumers may want to * visit the service provider in current site, thus consumers in site A visit service providers * in site A and consumers in site B visit service providers in site B. This is quite useful to * reduce the transfer delay of RPC. This is called same-site-prior strategy. *

Same Label Prior

* The same-site-prior strategy covers many circumstances in large companies and we can abstract * it to a higher level strategy: same-label-prior. *

* So the idea is that presumed we have built a self-defined or integrated a third-party idc CMDB * which stores all the labels of all IPs. Then we can filter provider IPs by the consumer IP and * we only return the providers who have the same label values with consumer. We can define the * labels we want to include in the comparison. *

* If no provider has the same label value with the consumer, we fall back to give all providers * to the consumer. Note that this fallback strategy may also be abstracted in future to introduce * more kinds of behaviors. * * @author nkorange * @see CmdbReader * @since 0.7.0 */ public class LabelSelector extends ExpressionSelector implements Selector { /** * The labels relevant to this the selector. * * @see com.alibaba.nacos.api.cmdb.pojo.Label */ private Set labels; private static final Set SUPPORTED_INNER_CONNCETORS = new HashSet<>(); private static final Set SUPPORTED_OUTER_CONNCETORS = new HashSet<>(); private static final String CONSUMER_PREFIX = "CONSUMER.label."; private static final String PROVIDER_PREFIX = "PROVIDER.label."; private static final char CEQUAL = '='; private static final char CAND = '&'; static { SUPPORTED_INNER_CONNCETORS.add(String.valueOf(CEQUAL)); SUPPORTED_OUTER_CONNCETORS.add(String.valueOf(CAND)); } public Set getLabels() { return labels; } public void setLabels(Set labels) { this.labels = labels; } public LabelSelector() { setType(SelectorType.label.name()); } private CmdbReader getCmdbReader() { return SpringContext.getAppContext().getBean(CmdbReader.class); } public static Set parseExpression(String expression) throws NacosException { return ExpressionInterpreter.parseExpression(expression); } @Override public List select(String consumer, List providers) { if (labels.isEmpty()) { return providers; } List instanceList = new ArrayList<>(); for (Instance instance : providers) { boolean matched = true; for (String labelName : getLabels()) { String consumerLabelValue = getCmdbReader().queryLabel(consumer, PreservedEntityTypes.ip.name(), labelName); if (StringUtils.isNotBlank(consumerLabelValue) && !StringUtils.equals(consumerLabelValue, getCmdbReader().queryLabel(instance.getIp(), PreservedEntityTypes.ip.name(), labelName))) { matched = false; break; } } if (matched) { instanceList.add(instance); } } if (instanceList.isEmpty()) { return providers; } return instanceList; } /** * Expression interpreter for label selector. *

* For now it supports very limited set of syntax rules. */ public static class ExpressionInterpreter { /** * Parse the label expression. *

* Currently we support the very single type of expression: *

         *     consumer.labelA = provider.labelA & consumer.labelB = provider.labelB
         * 
* Later we will implement a interpreter to parse this expression in a standard LL parser way. * * @param expression the label expression to parse * @return collection of labels */ public static Set parseExpression(String expression) throws NacosException { if (StringUtils.isBlank(expression)) { return new HashSet<>(); } expression = StringUtils.deleteWhitespace(expression); List elements = getTerms(expression); Set gotLabels = new HashSet<>(); int index = 0; index = checkInnerSyntax(elements, index); if (index == -1) { throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!"); } gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]); while (index < elements.size()) { index = checkOuterSyntax(elements, index); if (index >= elements.size()) { return gotLabels; } if (index == -1) { throw new NacosException(NacosException.INVALID_PARAM, "parse expression failed!"); } gotLabels.add(elements.get(index++).split(PROVIDER_PREFIX)[1]); } return gotLabels; } public static List getTerms(String expression) { List terms = new ArrayList<>(); Set characters = new HashSet<>(); characters.add(CEQUAL); characters.add(CAND); char[] chars = expression.toCharArray(); int lastIndex = 0; for (int index = 0; index < chars.length; index++) { char ch = chars[index]; if (characters.contains(ch)) { terms.add(expression.substring(lastIndex, index)); terms.add(expression.substring(index, index+1)); index ++; lastIndex = index; } } terms.add(expression.substring(lastIndex, chars.length)); return terms; } private static int skipEmpty(List elements, int start) { while (start < elements.size() && StringUtils.isBlank(elements.get(start))) { start++; } return start; } private static int checkOuterSyntax(List elements, int start) { int index = start; index = skipEmpty(elements, index); if (index >= elements.size()) { return index; } if (!SUPPORTED_OUTER_CONNCETORS.contains(elements.get(index++))) { return -1; } return checkInnerSyntax(elements, index); } private static int checkInnerSyntax(List elements, int start) { int index = start; index = skipEmpty(elements, index); if (index >= elements.size()) { return -1; } if (!elements.get(index).startsWith(CONSUMER_PREFIX)) { return -1; } String labelConsumer = elements.get(index++).split(CONSUMER_PREFIX)[1]; index = skipEmpty(elements, index); if (index >= elements.size()) { return -1; } if (!SUPPORTED_INNER_CONNCETORS.contains(elements.get(index++))) { return -1; } index = skipEmpty(elements, index); if (index >= elements.size()) { return -1; } if (!elements.get(index).startsWith(PROVIDER_PREFIX)) { return -1; } String labelProvider = elements.get(index).split(PROVIDER_PREFIX)[1]; if (!labelConsumer.equals(labelProvider)) { return -1; } return index; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy