中国电网Java面试被问:Dubbo的服务目录和路由链实现

一、服务目录(Directory)核心设计

1. 服务目录的核心作用

图表

代码

复制

下载

全屏

复制代码
graph TB
    A[服务消费者] --> B[服务目录Directory]
    B --> C[RegistryDirectory]
    B --> D[StaticDirectory]
    C --> E[服务发现]
    C --> F[配置变更监听]
    C --> G[Invoker列表维护]

2. RegistryDirectory核心实现

java

复制

下载

复制代码
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
    
    // 核心数据结构
    private final Map<String, Invoker<T>> urlInvokerMap = new ConcurrentHashMap<>();
    private final Map<String, List<Invoker<T>>> methodInvokerMap = new ConcurrentHashMap<>();
    
    // 服务发现与注册中心
    private Registry registry;
    private URL consumerUrl;
    
    /**
     * 订阅服务变更通知
     */
    @Override
    public synchronized void notify(List<URL> urls) {
        // 1. 服务提供者URL分类
        Map<String, List<URL>> categoryUrls = urls.stream()
            .filter(UrlUtils::isMatch)
            .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 "";
            }));
        
        // 2. 处理配置URL
        List<URL> configuratorUrls = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
        this.configurators = Configurator.toConfigurators(configuratorUrls);
        
        // 3. 处理路由URL
        List<URL> routerUrls = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
        toRouters(routerUrls).ifPresent(this::addRouters);
        
        // 4. 处理服务提供者URL
        List<URL> providerUrls = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
        refreshOverrideAndInvoker(providerUrls);
    }
    
    /**
     * 刷新Invoker列表
     */
    private void refreshOverrideAndInvoker(List<URL> urls) {
        // 1. 覆盖配置处理
        overrideDirectoryUrl();
        
        // 2. 转换为Invoker
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(urls);
        
        // 3. 状态转换
        if (newUrlInvokerMap == null || newUrlInvokerMap.isEmpty()) {
            // 服务不可用
            destroyAllInvokers();
        } else {
            // 更新Invoker映射
            this.urlInvokerMap = newUrlInvokerMap;
            
            // 4. 构建方法级Invoker映射
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);
            
            // 5. 合并多组Invoker
            this.methodInvokerMap = multiGroup ? toMergeInvokerList(newMethodInvokerMap) : newMethodInvokerMap;
            
            // 6. 销毁无用Invoker
            destroyUnusedInvokers(newUrlInvokerMap);
        }
    }
    
    /**
     * URL转Invoker
     */
    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
        Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<>();
        
        if (urls == null || urls.isEmpty()) {
            return newUrlInvokerMap;
        }
        
        Set<String> keys = new HashSet<>();
        
        for (URL url : urls) {
            // 应用配置规则
            URL configuredUrl = getConfiguredInvokerUrl(url);
            
            // 生成key: protocol://host:port/path?version
            String key = configuredUrl.toFullString();
            
            if (keys.contains(key)) {
                continue; // 去重
            }
            
            keys.add(key);
            
            // 缓存中获取或创建Invoker
            Invoker<T> invoker = invokersCache.get(key);
            if (invoker == null) {
                // 创建新的Invoker
                invoker = protocol.refer(serviceType, configuredUrl);
                invokersCache.put(key, invoker);
            }
            
            newUrlInvokerMap.put(key, invoker);
        }
        
        keys.clear();
        return newUrlInvokerMap;
    }
}

3. 服务目录的核心功能

3.1 服务发现与注册

java

复制

下载

复制代码
public class RegistryDirectory<T> extends AbstractDirectory<T> {
    
    // 订阅服务
    public void subscribe(URL url) {
        this.consumerUrl = url;
        // 注册消费者
        registry.register(consumerUrl);
        // 订阅服务提供者
        registry.subscribe(url, this);
    }
    
    // 取消订阅
    public void unsubscribe(URL url) {
        registry.unsubscribe(url, this);
        destroyAllInvokers();
    }
}
3.2 动态配置更新

java

复制

下载

复制代码
public class RegistryDirectory<T> extends AbstractDirectory<T> {
    
    // 配置变更监听
    private void refreshInvoker(List<URL> invokerUrls) {
        if (invokerUrls != null && invokerUrls.size() == 1 
                && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            // 服务提供者下线
            this.forbidden = true;
            this.methodInvokerMap = null;
            destroyAllInvokers();
        } else {
            this.forbidden = false;
            Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
            
            // 对比新旧URL,增量更新
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);
            
            // 找出需要销毁的Invoker
            List<Invoker<T>> destroyedInvokers = new ArrayList<>();
            for (Map.Entry<String, Invoker<T>> entry : localUrlInvokerMap.entrySet()) {
                if (!newUrlInvokerMap.containsKey(entry.getKey())) {
                    destroyedInvokers.add(entry.getValue());
                }
            }
            
            // 销毁无用Invoker
            for (Invoker<T> invoker : destroyedInvokers) {
                invoker.destroy();
                invokersCache.remove(invoker.getUrl().toFullString());
            }
        }
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

二、路由链(RouterChain)实现原理

1. 路由链的构建过程

java

复制

下载

复制代码
public class RouterChain<T> {
    
    private List<Router> routers = new ArrayList<>();
    
    // 构建路由链
    public static <T> RouterChain<T> buildChain(URL url) {
        RouterChain<T> chain = new RouterChain<>();
        
        // 1. 内置路由
        chain.addRouter(new TagRouter());
        chain.addRouter(new AppRouter());
        
        // 2. 配置的路由(从URL参数获取)
        String routerConfig = url.getParameter(Constants.ROUTER_KEY);
        if (StringUtils.isNotEmpty(routerConfig)) {
            String[] routerTypes = routerConfig.split(",");
            for (String routerType : routerTypes) {
                Router router = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                    .getExtension(routerType)
                    .getRouter(url);
                chain.addRouter(router);
            }
        }
        
        // 3. 脚本路由
        chain.addRouter(new ScriptRouter(url));
        
        // 4. 条件路由
        chain.addRouter(new ConditionRouter(url));
        
        return chain;
    }
    
    // 路由调用
    public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        
        // 依次通过各个路由筛选
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
            
            // 如果已经没有可用Invoker,提前终止
            if (finalInvokers == null || finalInvokers.isEmpty()) {
                return finalInvokers;
            }
        }
        
        return finalInvokers;
    }
}

2. 核心路由实现详解

2.1 条件路由(ConditionRouter)

java

复制

下载

复制代码
public class ConditionRouter implements Router {
    
    // 路由规则解析
    private final Map<String, MatchPair> whenCondition;
    private final Map<String, MatchPair> thenCondition;
    
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, 
                                      URL url, 
                                      Invocation invocation) {
        if (invokers == null || invokers.isEmpty()) {
            return invokers;
        }
        
        try {
            // 1. 判断是否匹配when条件
            if (!matchWhen(url, invocation)) {
                return invokers; // 不匹配,返回所有
            }
            
            List<Invoker<T>> result = new ArrayList<>();
            
            // 2. 应用then条件筛选
            for (Invoker<T> invoker : invokers) {
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            
            // 3. 如果有匹配的结果,返回;否则返回空
            if (!result.isEmpty()) {
                return result;
            } else if (thenCondition.isEmpty()) {
                // then条件为空表示黑名单,返回空
                return result;
            }
            
        } catch (Throwable t) {
            // 路由出错,返回所有
            return invokers;
        }
        
        return invokers;
    }
    
    // 条件匹配
    private boolean matchWhen(URL url, Invocation invocation) {
        return whenCondition == null || whenCondition.isEmpty() 
                || matchCondition(whenCondition, url, null, invocation);
    }
    
    // 模式匹配引擎
    private boolean matchCondition(Map<String, MatchPair> condition, 
                                   URL url, 
                                   URL param, 
                                   Invocation invocation) {
        Map<String, String> sample = url.toMap();
        
        for (Map.Entry<String, MatchPair> entry : condition.entrySet()) {
            String key = entry.getKey();
            MatchPair pair = entry.getValue();
            
            // 获取参数值
            String sampleValue = getSampleValue(key, sample, url, param, invocation);
            if (sampleValue == null) {
                // 参数不存在但规则要求必须匹配,返回false
                if (!pair.matches.isEmpty()) {
                    return false;
                }
                continue;
            }
            
            // 检查是否匹配
            if (!pair.isMatch(sampleValue)) {
                return false;
            }
        }
        
        return true;
    }
}
2.2 标签路由(TagRouter)

java

复制

下载

复制代码
public class TagRouter implements Router {
    
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, 
                                      URL url, 
                                      Invocation invocation) {
        if (invokers == null || invokers.isEmpty()) {
            return invokers;
        }
        
        // 1. 获取请求标签
        String tag = StringUtils.isNotEmpty(invocation.getAttachment(TAG_KEY)) 
                ? invocation.getAttachment(TAG_KEY) 
                : url.getParameter(TAG_KEY);
        
        // 2. 如果没有标签,过滤掉有标签的提供者
        if (StringUtils.isEmpty(tag)) {
            return invokers.stream()
                .filter(invoker -> {
                    String providerTag = invoker.getUrl().getParameter(TAG_KEY);
                    return StringUtils.isEmpty(providerTag);
                })
                .collect(Collectors.toList());
        }
        
        // 3. 匹配标签
        List<Invoker<T>> tagInvokers = invokers.stream()
            .filter(invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY)))
            .collect(Collectors.toList());
        
        // 4. 如果没有匹配的标签服务,降级到无标签服务
        if (!tagInvokers.isEmpty()) {
            return tagInvokers;
        } else {
            return invokers.stream()
                .filter(invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY)))
                .collect(Collectors.toList());
        }
    }
}

3. 路由链的扩展机制

java

复制

下载

复制代码
// 自定义路由实现
@SPI
public interface Router extends Comparable<Router> {
    
    int DEFAULT_PRIORITY = 0;
    
    /**
     * 路由方法
     */
    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, 
                               URL url, 
                               Invocation invocation);
    
    /**
     * 获取路由URL
     */
    URL getUrl();
    
    /**
     * 路由优先级,数值越小优先级越高
     */
    default int getPriority() {
        return DEFAULT_PRIORITY;
    }
}

// 路由工厂
@SPI
public interface RouterFactory {
    
    /**
     * 创建路由器
     */
    @Adaptive("protocol")
    Router getRouter(URL url);
}

// 扩展点配置
// META-INF/dubbo/internal/com.alibaba.dubbo.rpc.RouterFactory
condition=com.alibaba.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
script=com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouterFactory
file=com.alibaba.dubbo.rpc.cluster.router.file.FileRouterFactory

三、服务目录与路由链的协同工作

1. 完整的调用流程

java

复制

下载

复制代码
public abstract class AbstractDirectory<T> implements Directory<T> {
    
    @Override
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        // 1. 获取所有Invoker
        List<Invoker<T>> invokers = doList(invocation);
        
        // 2. 获取路由链
        List<Router> routers = getRouters();
        
        // 3. 应用路由链
        if (routers != null && !routers.isEmpty()) {
            for (Router router : routers) {
                invokers = router.route(invokers, getConsumerUrl(), invocation);
            }
        }
        
        // 4. 返回过滤后的Invoker
        return invokers == null ? Collections.emptyList() : invokers;
    }
    
    // 具体实现
    protected List<Invoker<T>> doList(Invocation invocation) {
        if (forbidden) {
            throw new RpcException("No provider available for service " + getInterface().getName());
        }
        
        List<Invoker<T>> invokers = null;
        
        // 从缓存中获取
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
            String methodName = invocation.getMethodName();
            invokers = localMethodInvokerMap.get(methodName);
            
            // 方法级路由
            if (invokers == null) {
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
        }
        
        return invokers;
    }
}

2. 动态配置更新机制

java

复制

下载

复制代码
public class RegistryDirectory<T> extends AbstractDirectory<T> {
    
    // 监听配置变更
    private void notify(List<URL> urls) {
        // 路由规则更新
        List<URL> routerUrls = filterRouter(urls);
        if (!routerUrls.isEmpty()) {
            // 更新路由链
            List<Router> routers = toRouters(routerUrls);
            if (routers != null) {
                setRouters(routers);
            }
        }
        
        // 配置规则更新
        List<URL> configuratorUrls = filterConfigurator(urls);
        if (!configuratorUrls.isEmpty()) {
            // 刷新配置
            refreshInvoker(invokerUrls);
        }
    }
    
    // 路由规则转换
    private Optional<List<Router>> toRouters(List<URL> routerUrls) {
        List<Router> routers = new ArrayList<>();
        
        for (URL routerUrl : routerUrls) {
            try {
                // 通过SPI加载路由工厂
                RouterFactory factory = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                    .getAdaptiveExtension();
                Router router = factory.getRouter(routerUrl);
                routers.add(router);
            } catch (Throwable t) {
                logger.error("Failed to create router from url: " + routerUrl, t);
            }
        }
        
        return Optional.of(routers);
    }
}

四、高级特性与优化

1. 路由缓存与性能优化

java

复制

下载

复制代码
public class RouterChain<T> {
    
    // 路由结果缓存
    private final Map<String, List<Invoker<T>>> routeCache = new LRUCache<>(1000);
    
    public List<Invoker<T>> route(URL url, Invocation invocation) {
        // 生成缓存key
        String cacheKey = generateCacheKey(url, invocation);
        
        // 尝试从缓存获取
        List<Invoker<T>> cachedResult = routeCache.get(cacheKey);
        if (cachedResult != null) {
            return cachedResult;
        }
        
        // 计算路由结果
        List<Invoker<T>> result = doRoute(url, invocation);
        
        // 放入缓存(设置合适的TTL)
        if (shouldCache(invocation)) {
            routeCache.put(cacheKey, result);
        }
        
        return result;
    }
    
    private String generateCacheKey(URL url, Invocation invocation) {
        StringBuilder sb = new StringBuilder();
        sb.append(url.getServiceKey())
          .append("#")
          .append(invocation.getMethodName())
          .append("#")
          .append(getParameterHash(url, invocation));
        return sb.toString();
    }
}

2. 灰度发布与路由策略

java

复制

下载

复制代码
// 基于权重的灰度路由
public class WeightRouter implements Router {
    
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, 
                                      URL url, 
                                      Invocation invocation) {
        
        // 计算总权重
        int totalWeight = 0;
        List<WeightedInvoker<T>> weightedInvokers = new ArrayList<>();
        
        for (Invoker<T> invoker : invokers) {
            int weight = invoker.getUrl().getParameter(WEIGHT_KEY, DEFAULT_WEIGHT);
            if (weight > 0) {
                totalWeight += weight;
                weightedInvokers.add(new WeightedInvoker<>(invoker, weight));
            }
        }
        
        // 随机选择
        if (totalWeight > 0) {
            int random = ThreadLocalRandom.current().nextInt(totalWeight);
            int current = 0;
            
            for (WeightedInvoker<T> weightedInvoker : weightedInvokers) {
                current += weightedInvoker.weight;
                if (random < current) {
                    return Collections.singletonList(weightedInvoker.invoker);
                }
            }
        }
        
        // 降级到随机选择
        return invokers;
    }
    
    private static class WeightedInvoker<T> {
        final Invoker<T> invoker;
        final int weight;
        
        WeightedInvoker(Invoker<T> invoker, int weight) {
            this.invoker = invoker;
            this.weight = weight;
        }
    }
}

3. 故障转移与熔断路由

java

复制

下载

复制代码
public class FailoverRouter implements Router {
    
    private final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.getInstance();
    
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, 
                                      URL url, 
                                      Invocation invocation) {
        
        return invokers.stream()
            .filter(invoker -> {
                // 检查熔断器状态
                String serviceKey = invoker.getUrl().getServiceKey();
                CircuitBreaker circuitBreaker = circuitBreakerRegistry.getCircuitBreaker(serviceKey);
                
                if (circuitBreaker != null && circuitBreaker.isOpen()) {
                    // 熔断器打开,跳过该服务
                    return false;
                }
                
                // 检查健康状态
                return isHealthy(invoker);
            })
            .collect(Collectors.toList());
    }
    
    private <T> boolean isHealthy(Invoker<T> invoker) {
        // 检查最近错误率
        // 检查响应时间
        // 检查连接状态
        return true;
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

五、最佳实践与配置

1. 配置示例

xml

复制

下载

运行

复制代码
<!-- 服务消费者配置 -->
<dubbo:reference id="userService" interface="com.example.UserService">
    <!-- 路由规则 -->
    <dubbo:parameter key="router" value="tag,condition" />
    
    <!-- 条件路由规则 -->
    <dubbo:parameter key="rule" value="
        => host != 192.168.1.100
        & method = find* => host = 192.168.1.*
        & version = 2.0 => cluster = cluster1
    " />
    
    <!-- 标签路由 -->
    <dubbo:parameter key="tag" value="gray" />
</dubbo:reference>

<!-- 动态配置 -->
<dubbo:config-center address="zookeeper://127.0.0.1:2181">
    <dubbo:parameter key="router" value="
        scope: service
        key: com.example.UserService
        enabled: true
        rules:
          - priority: 1
            conditions:
              - method=find* => host=192.168.1.*
          - priority: 2  
            conditions:
              - => tag=gray
    " />
</dubbo:config-center>

2. 监控与调试

java

复制

下载

复制代码
// 路由链监控
public class RouterChainMonitor {
    
    public void monitorRouteChain(RouterChain<?> chain, Invocation invocation) {
        List<Invoker<?>> initialInvokers = chain.getInitialInvokers();
        Map<String, List<Invoker<?>>> routeHistory = new LinkedHashMap<>();
        
        // 记录每个路由器的输出
        for (Router router : chain.getRouters()) {
            List<Invoker<?>> before = routeHistory.isEmpty() 
                    ? initialInvokers 
                    : routeHistory.values().iterator().next();
            
            List<Invoker<?>> after = router.route(before, getUrl(), invocation);
            
            routeHistory.put(router.getClass().getSimpleName(), after);
            
            // 记录到监控系统
            recordRouteMetrics(router, before.size(), after.size());
        }
        
        // 输出路由链详情
        logRouteChainDetail(routeHistory);
    }
}

六、总结

Dubbo服务目录与路由链的核心要点:

  1. 服务目录

    • 负责维护可用的服务提供者列表

    • 监听注册中心变化,动态更新Invoker

    • 提供方法级的服务分组

  2. 路由链

    • 通过责任链模式实现多重路由规则

    • 支持条件路由、标签路由、脚本路由等

    • 路由优先级控制执行顺序

  3. 关键特性

    • 动态更新:配置热更新,无需重启

    • 扩展性强:SPI机制支持自定义路由

    • 性能优化:缓存、懒加载等优化手段

    • 容错机制:路由失败降级策略

  4. 最佳实践

    • 合理设计路由规则,避免过度复杂

    • 监控路由链性能,及时优化

    • 结合业务场景选择合适的路由策略

这种设计使得Dubbo能够实现复杂的流量治理需求,如灰度发布、蓝绿部署、区域路由等高级功能。

相关推荐
爬山算法2 小时前
Hibernate(42)在Hibernate中如何实现分页?
java·后端·hibernate
不平衡的叉叉树2 小时前
我们遇到了正则表达式的灾难性回溯问题
java·正则表达式
itwangyang5202 小时前
人工智能药物设计和生信常用 R 包一键全自动安装脚本
开发语言·人工智能·r语言
catchadmin2 小时前
PHP 8.5 升级生存指南:避免凌晨两点回滚的检查清单
开发语言·php
3824278272 小时前
JS正则表达式实战:核心语法解析
开发语言·前端·javascript·python·html
zh_xuan2 小时前
kotlin伴生对象
开发语言·kotlin
缘来是黎2 小时前
运维面试场景题——故障排查与解决
运维·面试·职场和发展
wangkay882 小时前
【Java 转运营】Day05:抖音新号起号:对标账号运营全指南
java·新媒体运营
你怎么知道我是队长2 小时前
C语言---递归
c语言·开发语言