org.apache.dubbo.rpc.cluster.SingleRouterChain Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dubbo Show documentation
Show all versions of dubbo Show documentation
The all in one project of dubbo
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.cluster.router.RouterResult;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotSwitcher;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
import org.apache.dubbo.rpc.cluster.router.state.TailStateRouter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_STOP;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_NO_VALID_PROVIDER;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.INTERNAL_ERROR;
/**
* Router chain
*/
public class SingleRouterChain {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(SingleRouterChain.class);
/**
* full list of addresses from registry, classified by method name.
*/
private volatile BitList> invokers = BitList.emptyList();
/**
* containing all routers, reconstruct every time 'route://' urls change.
*/
private volatile List routers = Collections.emptyList();
/**
* Fixed router instances: ConfigConditionRouter, TagRouter, e.g.,
* the rule for each instance may change but the instance will never delete or recreate.
*/
private volatile List builtinRouters = Collections.emptyList();
private volatile StateRouter headStateRouter;
private volatile List> stateRouters;
/**
* Should continue route if current router's result is empty
*/
private final boolean shouldFailFast;
private final RouterSnapshotSwitcher routerSnapshotSwitcher;
private final AtomicInteger currentConcurrency = new AtomicInteger(0);
public SingleRouterChain(List routers, List> stateRouters, boolean shouldFailFast, RouterSnapshotSwitcher routerSnapshotSwitcher) {
initWithRouters(routers);
initWithStateRouters(stateRouters);
this.shouldFailFast = shouldFailFast;
this.routerSnapshotSwitcher = routerSnapshotSwitcher;
}
private void initWithStateRouters(List> stateRouters) {
StateRouter stateRouter = TailStateRouter.getInstance();
for (int i = stateRouters.size() - 1; i >= 0; i--) {
StateRouter nextStateRouter = stateRouters.get(i);
nextStateRouter.setNextRouter(stateRouter);
stateRouter = nextStateRouter;
}
this.headStateRouter = stateRouter;
this.stateRouters = Collections.unmodifiableList(stateRouters);
}
/**
* the resident routers must being initialized before address notification.
* only for ut
*/
public void initWithRouters(List builtinRouters) {
this.builtinRouters = builtinRouters;
this.routers = new LinkedList<>(builtinRouters);
}
/**
* If we use route:// protocol in version before 2.7.0, each URL will generate a Router instance, so we should
* keep the routers up to date, that is, each time router URLs changes, we should update the routers list, only
* keep the builtinRouters which are available all the time and the latest notified routers which are generated
* from URLs.
*
* @param routers routers from 'router://' rules in 2.6.x or before.
*/
public void addRouters(List routers) {
List newRouters = new LinkedList<>();
newRouters.addAll(builtinRouters);
newRouters.addAll(routers);
CollectionUtils.sort(newRouters);
this.routers = newRouters;
}
public List getRouters() {
return routers;
}
public StateRouter getHeadStateRouter() {
return headStateRouter;
}
public List> route(URL url, BitList> availableInvokers, Invocation invocation) {
currentConcurrency.incrementAndGet();
try {
if (invokers.getOriginList() != availableInvokers.getOriginList()) {
logger.error(INTERNAL_ERROR, "", "Router's invoker size: " + invokers.getOriginList().size() +
" Invocation's invoker size: " + availableInvokers.getOriginList().size(),
"Reject to route, because the invokers has changed.");
throw new IllegalStateException("reject to route, because the invokers has changed.");
}
if (RpcContext.getServiceContext().isNeedPrintRouterSnapshot()) {
return routeAndPrint(url, availableInvokers, invocation);
} else {
return simpleRoute(url, availableInvokers, invocation);
}
} finally {
currentConcurrency.decrementAndGet();
}
}
public List> routeAndPrint(URL url, BitList> availableInvokers, Invocation invocation) {
RouterSnapshotNode snapshot = buildRouterSnapshot(url, availableInvokers, invocation);
logRouterSnapshot(url, invocation, snapshot);
return snapshot.getChainOutputInvokers();
}
public List> simpleRoute(URL url, BitList> availableInvokers, Invocation invocation) {
BitList> resultInvokers = availableInvokers.clone();
// 1. route state router
resultInvokers = headStateRouter.route(resultInvokers, url, invocation, false, null);
if (resultInvokers.isEmpty() && (shouldFailFast || routers.isEmpty())) {
printRouterSnapshot(url, availableInvokers, invocation);
return BitList.emptyList();
}
if (routers.isEmpty()) {
return resultInvokers;
}
List> commonRouterResult = resultInvokers.cloneToArrayList();
// 2. route common router
for (Router router : routers) {
// Copy resultInvokers to a arrayList. BitList not support
RouterResult> routeResult = router.route(commonRouterResult, url, invocation, false);
commonRouterResult = routeResult.getResult();
if (CollectionUtils.isEmpty(commonRouterResult) && shouldFailFast) {
printRouterSnapshot(url, availableInvokers, invocation);
return BitList.emptyList();
}
// stop continue routing
if (!routeResult.isNeedContinueRoute()) {
return commonRouterResult;
}
}
if (commonRouterResult.isEmpty()) {
printRouterSnapshot(url, availableInvokers, invocation);
return BitList.emptyList();
}
return commonRouterResult;
}
/**
* store each router's input and output, log out if empty
*/
private void printRouterSnapshot(URL url, BitList> availableInvokers, Invocation invocation) {
if (logger.isWarnEnabled()) {
logRouterSnapshot(url, invocation, buildRouterSnapshot(url, availableInvokers, invocation));
}
}
/**
* Build each router's result
*/
public RouterSnapshotNode buildRouterSnapshot(URL url, BitList> availableInvokers, Invocation invocation) {
BitList> resultInvokers = availableInvokers.clone();
RouterSnapshotNode parentNode = new RouterSnapshotNode("Parent", resultInvokers.clone());
parentNode.setNodeOutputInvokers(resultInvokers.clone());
// 1. route state router
Holder> nodeHolder = new Holder<>();
nodeHolder.set(parentNode);
resultInvokers = headStateRouter.route(resultInvokers, url, invocation, true, nodeHolder);
// result is empty, log out
if (routers.isEmpty() || (resultInvokers.isEmpty() && shouldFailFast)) {
parentNode.setChainOutputInvokers(resultInvokers.clone());
return parentNode;
}
RouterSnapshotNode commonRouterNode = new RouterSnapshotNode("CommonRouter", resultInvokers.clone());
parentNode.appendNode(commonRouterNode);
List> commonRouterResult = resultInvokers;
// 2. route common router
for (Router router : routers) {
// Copy resultInvokers to a arrayList. BitList not support
List> inputInvokers = new ArrayList<>(commonRouterResult);
RouterSnapshotNode currentNode = new RouterSnapshotNode(router.getClass().getSimpleName(), inputInvokers);
// append to router node chain
commonRouterNode.appendNode(currentNode);
commonRouterNode = currentNode;
RouterResult> routeStateResult = router.route(inputInvokers, url, invocation, true);
List> routeResult = routeStateResult.getResult();
String routerMessage = routeStateResult.getMessage();
currentNode.setNodeOutputInvokers(routeResult);
currentNode.setRouterMessage(routerMessage);
commonRouterResult = routeResult;
// result is empty, log out
if (CollectionUtils.isEmpty(routeResult) && shouldFailFast) {
break;
}
if (!routeStateResult.isNeedContinueRoute()) {
break;
}
}
commonRouterNode.setChainOutputInvokers(commonRouterNode.getNodeOutputInvokers());
// 3. set router chain output reverse
RouterSnapshotNode currentNode = commonRouterNode;
while (currentNode != null){
RouterSnapshotNode parent = currentNode.getParentNode();
if (parent != null) {
// common router only has one child invoke
parent.setChainOutputInvokers(currentNode.getChainOutputInvokers());
}
currentNode = parent;
}
return parentNode;
}
private void logRouterSnapshot(URL url, Invocation invocation, RouterSnapshotNode snapshotNode) {
if (snapshotNode.getChainOutputInvokers() == null ||
snapshotNode.getChainOutputInvokers().isEmpty()) {
if (logger.isWarnEnabled()) {
String message = "No provider available after route for the service " + url.getServiceKey()
+ " from registry " + url.getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Router snapshot is below: \n" + snapshotNode.toString();
if (routerSnapshotSwitcher.isEnable()) {
routerSnapshotSwitcher.setSnapshot(message);
}
logger.warn(CLUSTER_NO_VALID_PROVIDER,"No provider available after route for the service","",message);
}
} else {
if (logger.isInfoEnabled()) {
String message = "Router snapshot service " + url.getServiceKey()
+ " from registry " + url.getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + " is below: \n" + snapshotNode.toString();
if (routerSnapshotSwitcher.isEnable()) {
routerSnapshotSwitcher.setSnapshot(message);
}
logger.info(message);
}
}
}
/**
* Notify router chain of the initial addresses from registry at the first time.
* Notify whenever addresses in registry change.
*/
public void setInvokers(BitList> invokers) {
this.invokers = (invokers == null ? BitList.emptyList() : invokers);
routers.forEach(router -> router.notify(this.invokers));
stateRouters.forEach(router -> router.notify(this.invokers));
}
/**
* for uts only
*/
@Deprecated
public void setHeadStateRouter(StateRouter headStateRouter) {
this.headStateRouter = headStateRouter;
}
/**
* for uts only
*/
@Deprecated
public List> getStateRouters() {
return stateRouters;
}
public int getCurrentConcurrency() {
return currentConcurrency.get();
}
public void destroy() {
invokers = BitList.emptyList();
for (Router router : routers) {
try {
router.stop();
} catch (Exception e) {
logger.error(CLUSTER_FAILED_STOP,"route stop failed","","Error trying to stop router " + router.getClass(),e);
}
}
routers = Collections.emptyList();
builtinRouters = Collections.emptyList();
for (StateRouter router : stateRouters) {
try {
router.stop();
} catch (Exception e) {
logger.error(CLUSTER_FAILED_STOP,"StateRouter stop failed","","Error trying to stop StateRouter " + router.getClass(),e);
}
}
stateRouters = Collections.emptyList();
headStateRouter = TailStateRouter.getInstance();
}
}