What is service routing
Service routing contains a routing rule that determines the invocation target of the service consumer, that is, which service providers can be invoked by the service consumer. Dubbo currently provides three service routing implementations, which are:
- ConditionRouter: ConditionRouter (most commonly used)
- Script routing: ScriptRouter
- TagRoute: TagRouter
Routing rules
Apply granular routing rules:
# application stands for application granularityscope: application # Specifies whether to force the route if the result is empty. If the route result is empty, the routing rule is invalid automatically and no route is used. Default isfalse, it is not mandatory.force: true# specifies whether the routing rule is executed on each invocation. iffalseIt means that the routing rule is pre-executed only when the provider address list changes, and the routing result is cached. When the consumer calls, the routing result is directly obtained from the cache. # iftrueIt indicates that the routing rules must be recalculated for each invocation, which directly affects the invocation performance. Default isfalse.runtime: true
enabled: true# Set the priority of the routing rule. The higher the number, the higher the priority and the higher the execution. Default is0.priority: 0# Routing rules due to the service namekey: governance-conditionrouter-consumer To define specific routing rules, run the following command1To any number of rules.conditions: # app1 consumers can only consume all ports as20880- Application =app1= > address=*:20880# app2 consumers can only consume all ports as20881- Application =app2= > address=*:20881
Copy the code
Service granularity routing rules:
scope: service
force: true
runtime: true
enabled: trueThe name of the specified consumer interfacekey: org.apache.dubbo.samples.governance.api.DemoService
conditionsThe sayHello method of DemoService can only consume all ports as20880- method=sayHello= > address=*:20880The sayHi method of # DemoService can only consume all ports as20881- method=sayHi= > address=*:20881
Copy the code
format
The instance
-
The blacklist
The host = 10.20.153.10, 10.20.153.11 = >
The hosts whose IP addresses are 10.20.153.10 and 10.20.153.11 are disabled.
-
White list
host ! = 10.20.153.10, 10.20.153.11 = >
Disable all hosts whose IP addresses are not 10.20.153.10 and 10.20.153.11.
-
Only a subset of providers are exposed
= = > host 172.22.3.1 *, 172.22.3.2 *
The consumer can access only the provider hosts with IP addresses 172.22.3.1 and 172.22.3.2.
-
Provide additional machines for critical applications
application ! = kylin => host ! = 172.22.3.95, 172.22.3.96
The application whose name is not Kylin cannot access 172.22.3.95 and 172.22.3.96. That is, only the consumer named Kylin can access the two provider hosts 172.22.3.95 and 172.22.3.96. Of course, Kylin has access to other provider hosts, and other consumers have access to all provider hosts except 172.22.3.95 and 172.22.3.96.
-
Reading and writing separation
Method = find *, list *, get * is * = > host = 172.22.3.94, 172.22.3.95, 172.22.3.96
method ! * = find *, list the get *, is * = > host = 172.22.3.97, 172.22.3.98
Consumer methods starting with find, list, get, and IS are routed to 172.22.3.94, 172.22.3.95, and 172.22.3.96. The other methods are routed to 172.22.3.97 and 172.22.3.98 provider hosts
-
Front and background separation
Application = bops = > host = 172.22.3.91, 172.22.3.92, 172.22.3.93
application ! = bops = > host = 172.22.3.94 172.22.3.95, 172.22.3.96
Consumers of application name bOPS are routed to 172.22.3.91, 172.22.3.92, and 172.22.3.93 provider hosts. Other consumers are routed to 172.22.3.94, 172.22.3.95, and 172.22.3.96 providers.
-
Isolate different network segments of the equipment room
host ! = 172.22.3.* => host! = 172.22.3. *
Consumers that are not in the 172.22.3 network segment cannot access providers in the 172.22.3 network segment. That is, only consumers on 172.22.3 can access providers on 172.22.3. Of course, consumers in 172.22.3 can also access providers in other network segments.
-
Access only local services
=> host = $host
$host gets the consumer host IP in the consumer request. Therefore, this rule means that the consumer can only access the local service.
Add activated RouterFactory to Directory
RegistryProtocol.java
RegistryDirectory.java
public void buildRouterChain(URL url) {
// Parse the routing information in the URL
this.setRouterChain(RouterChain.buildChain(url));
}
Copy the code
RouterChain.java
public static <T> RouterChain<T> buildChain(URL url) {
return new RouterChain<>(url);
}
private RouterChain(URL url) {
// Get all active class instances of RouterFactory
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
.getActivateExtension(url, (String[]) null);
List<Router> routers = extensionFactories.stream() // Generate a Stream with factory element
.map(factory -> factory.getRouter(url)) // Map the factory in the Stream to the router generated by that factory
.collect(Collectors.toList()); // Change Stream to List
// Initializes the created router list into the Directory's routerChain
initWithRouters(routers);
}
Copy the code
Read routing rules in the registry
private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
// Get all non-empty urls under the current category node
List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
// If there are no children under the current classification node, the system creates an empty://... The empty url
if (urls == null || urls.isEmpty()) {
int i = path.lastIndexOf(PATH_SEPARATOR);
String category = i < 0 ? path : path.substring(i + 1);
URL empty = URLBuilder.from(consumer)
.setProtocol(EMPTY_PROTOCOL)
.addParameter(CATEGORY_KEY, category)
.build();
urls.add(empty);
}
return urls;
}
private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
List<URL> urls = new ArrayList<>();
if (CollectionUtils.isNotEmpty(providers)) {
// Iterate over all child nodes
for (String provider : providers) {
/ / decoding
provider = URL.decode(provider);
// Handle the case where the child node names are in the form of urls
if (provider.contains(PROTOCOL_SEPARATOR)) {
URL url = URL.valueOf(provider);
if(UrlUtils.isMatch(consumer, url)) { urls.add(url); }}}}return urls;
}
Copy the code
RegistryDirectory.java
@Override
public synchronized void notify(List<URL> urls) {
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(url -> {
if (UrlUtils.isConfigurator(url)) {
return CONFIGURATORS_CATEGORY;
} else if (UrlUtils.isRoute(url)) {
return ROUTERS_CATEGORY;
} else if (UrlUtils.isProvider(url)) {
return PROVIDERS_CATEGORY;
}
return "";
}));
// Handle configurators
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
// Process the classified nodes of the Routers
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
toRouters(routerURLs) / / returns the Optional
// If Optional encapsulates an object that is not empty, call Lambda's instance method to reference addRouters(), adding the route to directory
.ifPresent(this::addRouters);
// providers
// Handle providers classification nodes
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
// Get the latest provider updates from ZK to directory
refreshOverrideAndInvoker(providerURLs);
}
private void refreshOverrideAndInvoker(List<URL> urls) {
// mock zookeeper://xxx? mock=return null
overrideDirectoryUrl();
refreshInvoker(urls); //
}
private Optional<List<Router>> toRouters(List<URL> urls) {
if (urls == null || urls.isEmpty()) {
return Optional.empty();
}
List<Router> routers = new ArrayList<>();
for (URL url : urls) {
// If the current URL is empty as protocol, it is not a valid URL
if (EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
// Obtain the router attribute value in the URL. The current value is condition, indicating that this is a conditional route
String routerType = url.getParameter(ROUTER_KEY);
if(routerType ! =null && routerType.length() > 0) {
// the route type is the protocol of the URL, i.e. the current URL is changed to condition://...
url = url.setProtocol(routerType);
}
try {
// Create a route
Router router = ROUTER_FACTORY.getRouter(url);
// Record the route instance
if (!routers.contains(router)) {
routers.add(router);
}
} catch (Throwable t) {
logger.error("convert router url to router error, url: "+ url, t); }}return Optional.of(routers);
}
Copy the code
Routing Rules Take effect
Occurs when a method is called
InvokerInvocationHandler.java
public class InvokerInvocationHandler implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(InvokerInvocationHandler.class);
private finalInvoker<? > invoker;public InvokerInvocationHandler(Invoker
handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Get the method name of the RPC remote callString methodName = method.getName(); Class<? >[] parameterTypes = method.getParameterTypes();// If the current method is an Object method, it is a local method call
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
// Remote call
return invoker.invoke(newRpcInvocation(method, args)).recreate(); }}Copy the code
MockClusterInvoker.java
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock remote invocation
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
result = doMockInvoke(invocation, null);
} else {
//fail-mock
try { // Remote call
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
}
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : "+ directory.getUrl(), e); } result = doMockInvoke(invocation, e); }}return result;
}
Copy the code
AbstractClusterInvoker.java
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if(contextAttachments ! =null&& contextAttachments.size() ! =0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// Routing: Filter out invokers that are not available according to routing rules and return the remaining invokers that are available
// Trace the list
List<Invoker<T>> invokers = list(invocation);
// Obtain the load balancing policy
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
// Invoke doInvoke() with a specific fault-tolerant policy
return doInvoke(invocation, invokers, loadbalance);
}
Copy the code
RegistryDirectory.java
public List<Invoker<T>> doList(Invocation invocation) {
if (forbidden) {
// 1. No service provider 2. Service providers are disabled
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
", please check status of providers(disabled, not registered or in blacklist).");
}
if (multiGroup) {
return this.invokers == null ? Collections.emptyList() : this.invokers;
}
List<Invoker<T>> invokers = null;
try {
// Get invokers from cache, only runtime routers will be executed.
// Apply routing rules to all invokers to filter out invokers that do not meet the rules
invokers = routerChain.route(getConsumerUrl(), invocation);
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
return invokers == null ? Collections.emptyList() : invokers;
}
Copy the code
RouterChain.java
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
Copy the code
ConditionRouter.java
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
// If the enabled attribute of the routing rule is false(i.e. the current routing rule equals none), all invokers are returned
if(! enabled) {return invokers;
}
// Return null if there is no provider
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
// The matcherWhen() method is used to determine whether the current consumer matches the previous part of the arrow (=>),
// If the rule does not match, the current rule does not apply to the current consumer, that is, all invokers are directly returned without routing filtering
if(! matchWhen(url, invocation)) {return invokers;
}
// The current consumer has been matched with the front part of the arrow (=>), now look at the situation behind the arrow (=>)
// This result set will be used to store invokers filtered by the route
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
// thenCondition stores the following part of the arrow (=>).
// The current rule is black/white, that is, all invokers are unavailable
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
// The current consumer is matched in front of the arrow (=>) and is not empty.
// Now you need to iterate through all invokers to try to match the rule condition after the arrow (=>)
for (Invoker<T> invoker : invokers) {
// matchThen() determines if it matches the condition after the arrow (=>)
if (matchThen(invoker.getUrl(), url)) {
// If the match is matched, the route is filtered to indicate that the match is availableresult.add(invoker); }}if(! result.isEmpty()) {return result;
} else if (force) {
logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
returnresult; }}catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
return invokers;
}
Copy the code