dubbo 一些面试题 - litter-fish/ReadSource GitHub Wiki

1、dubbo中"读接口"和"写接口"有什么区别?

因为默认FailoverCluster会重试, 如果是"写"类型的接口, 会在网络抖动情况下写入多个值, 所以"写"类型的接口要换成FailfastCluster

2、谈谈dubbo中的负载均衡算法及特点?

RandomLoadBalance(随机):随机,按权重设置随机概率
RoundRobinLoadBalance(轮询):轮循,按公约后的权重设置轮循比率。
LeastActiveLoadBalance(最少活跃数):
ConsistentHashLoadBalance(一致性哈希):
    一致性 Hash,相同参数的请求总是发到同一提供者。
    当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
    缺省只对第一个参数 Hash,如果要修改,请配置
        <dubbo:parameter key="hash.arguments" value="0,1" />
    缺省用 160 份虚拟节点,如果要修改,请配置
        <dubbo:parameter key="hash.nodes" value="320" />

3、最小活跃数算法中是如何统计这个活跃数的?

dubbo:service中还需要配置filter="activelimit"
活跃数的变化是在com.alibaba.dubbo.rpc.filter.ActiveLimitFilter中,如果没有配置dubbo:reference的actives属性,默认是调用前活跃数+1,调用结束-1
@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String methodName = invocation.getMethodName();
        int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
        RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
        if (max > 0) {
            long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
            long start = System.currentTimeMillis();
            long remain = timeout;
            int active = count.getActive();
            if (active >= max) {
                synchronized (count) {
                    while ((active = count.getActive()) >= max) {
                        try {
                            count.wait(remain);
                        } catch (InterruptedException e) {
                        }
                        long elapsed = System.currentTimeMillis() - start;
                        remain = timeout - elapsed;
                        if (remain <= 0) {
                            throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                    + invoker.getInterface().getName() + ", method: "
                                    + invocation.getMethodName() + ", elapsed: " + elapsed
                                    + ", timeout: " + timeout + ". concurrent invokes: " + active
                                    + ". max concurrent invoke limit: " + max);
                        }
                    }
                }
            }
        }
        try {
            long begin = System.currentTimeMillis();
            RpcStatus.beginCount(url, methodName);
            try {
                Result result = invoker.invoke(invocation);
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
                return result;
            } catch (RuntimeException t) {
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
                throw t;
            }
        } finally {
            if (max > 0) {
                synchronized (count) {
                    count.notify();
                }
            }
        }
    }

}

4、简单谈谈你对一致性哈希算法的认识?

原理: 先将服务根据规则将值转换成一个分布在[0, 2^32-1]的数字,获取第一个比他大的值
down机影响:只影响了一个节点,其他节点不受影响
虚拟节点:一般的Hash函数对于节点在圆环上的映射,并不均匀.所以我们需要引入虚拟节点,假如有N个真实节点,把每个真实节点映射成M个虚拟节点,再把 M*N 个虚拟节点, 散列在圆环上。

5、服务发布过程中做了哪些事?

1. 暴露本地服务
2. 暴露远程服务
3. 启动netty
4. 连接zookeeper
5. 到zookeeper注册
6. 监听zookeeper

b8563e17b1c6c478e895f868acb7fca6.png

6、dubbo都有哪些协议,他们之间有什么特点,缺省值是什么?

  1. dubbo支持多种协议, 默认使用的是dubbo协议, Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况, 反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低
  2. RMI 协议采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式。
  3. Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
  4. 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现
  5. 基于 WebService 的远程调用协议,基于 Apache CXF 的 frontend-simple 和 transports-http 实现。 可以和原生 WebService 服务互操作,即: 提供者用 Dubbo 的 WebService 协议暴露服务,消费者直接用标准 WebService 接口调用, 或者提供方用标准 WebService 暴露服务,消费方用 Dubbo 的 WebService 协议调用。
  6. dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展, 在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等
  7. 基于 memcached 实现的 RPC 协议
  8. 基于 Redis 实现的 RPC 协议
特性       
协议名称 连接个数 连接方式 传输协议 传输方式 序列化 适用范围 适用场景
dubbo 单连接 长连接 TCP NIO 异步传输 Hessian 二进制序列化 传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。 常规远程服务方法调用
rmi 多连接 短连接 TCP 同步传输 Java 标准二进制序列化 传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。 常规远程服务方法调用,与原生RMI服务互操作
hessian 多连接 短连接 HTTP 同步传输 Hessian二进制序列化 传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件 页面传输,文件传输,或与原生hessian服务互操作
http 多连接 短连接 HTTP 同步传输 表单序列化 传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。 需同时给应用程序和浏览器 JS 使用的服务。
webservice 多连接 短连接 HTTP 同步传输 SOAP 文本序列化 系统集成,跨语言调用

7、什么是本地暴露和远程暴露,他们的区别?

一个服务可能既是Provider,又是Consumer,因此就存在他自己调用自己服务的情况
1. 本地暴露是暴露在JVM中,不需要网络通信,url是以injvm开头。
2. 远程暴露是将ip,端口等信息暴露给远程客户端,调用时需要网络通信。

本地服务的暴露
  1. 具体服务到invoker
private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(
            url.getProtocol())) {
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        ServiceClassHolder.getInstance()
            .pushServiceClass(getServiceClass(ref));
        Exporter<?> exporter = protocol.export(
                proxyFactory.getInvoker(
                    ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}

SPI生成代码

public class ProxyFactory$Adaptive implements
            com.alibaba.dubbo.rpc.ProxyFactory {
    public com.alibaba.dubbo.rpc.Invoker getInvoker(
            java.lang.Object arg0, java.lang.Class arg1, 
            com.alibaba.dubbo.common.URL arg2)
                throws com.alibaba.dubbo.rpc.RpcException {
        if (arg2 == null)
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null)
            throw new IllegalStateException("...");
        com.alibaba.dubbo.rpc.ProxyFactory extension =
                (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader
                        .getExtensionLoader(
                          com.alibaba.dubbo.rpc.ProxyFactory.class)
                        .getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

public class StubProxyFactoryWrapper implements ProxyFactory {
    public <T> Invoker<T> getInvoker(
        T proxy, Class<T> type, URL url) throws RpcException {
        return proxyFactory.getInvoker(proxy, type, url);
    }
}

public class JavassistProxyFactory extends AbstractProxyFactory {
    @Override
    public <T> Invoker<T> getInvoker(
                T proxy, Class<T> type, URL url) {
        final Wrapper wrapper =
            Wrapper.getWrapper(
                proxy.getClass().getName().indexOf('$') < 0 
            ? proxy.getClass() 
            : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return
                    wrapper.invokeMethod(
                        proxy, methodName, parameterTypes, arguments);
            }
        };
    }
}
    2. Invoker转换为Exporter
public class Protocol$Adaptive implements 
        com.alibaba.dubbo.rpc.Protocol {
    public com.alibaba.dubbo.rpc.Exporter export(
            com.alibaba.dubbo.rpc.Invoker arg0)
                throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("...");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("...");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null 
            ? "dubbo" 
            : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("...");
        com.alibaba.dubbo.rpc.Protocol extension =
            (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader
                .getExtensionLoader(
                    com.alibaba.dubbo.rpc.Protocol.class)
                .getExtension(extName);
        return extension.export(arg0);
    }
}

public class ProtocolFilterWrapper implements Protocol {
    public <T> Exporter<T> export(Invoker<T> invoker)
            throws RpcException {
        if (
            Constants.REGISTRY_PROTOCOL.equals(
                invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return protocol.export(
            buildInvokerChain(invoker, 
                Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
}

private static <T> Invoker<T> buildInvokerChain(
        final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // dubbo默认提供8个过滤器
    // EchoFilter、ClassLoaderFilter、GenericFilter、ContextFilter、
    // TraceFilter、TimeoutFilter、MonitorFilter、ExceptionFilter
    List<Filter> filters =
        ExtensionLoader
            .getExtensionLoader(Filter.class)
            .getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
                ......
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
                ......
            };
        }
    }
    return last;
}

public class ProtocolListenerWrapper implements Protocol {
    public <T> Exporter<T> export(Invoker<T> invoker) 
            throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(
                invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return new ListenerExporterWrapper<T>(
            protocol.export(invoker),
                Collections.unmodifiableList(
                    ExtensionLoader
                        .getExtensionLoader(
                            ExporterListener.class)
                        .getActivateExtension(
                            invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }
}

public class InjvmProtocol extends AbstractProtocol 
        implements Protocol {
    public <T> Exporter<T> export(Invoker<T> invoker) 
            throws RpcException {
        return new InjvmExporter<T>(
                invoker,
                invoker.getUrl().getServiceKey(), exporterMap);
    }
}

class InjvmExporter<T> extends AbstractExporter<T> {
    InjvmExporter(
        Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
        // 缓存exporter
        exporterMap.put(key, this);
    }
}

8、服务提供者能实现失效踢出是根据什么原理?

9、讲讲dubbo服务暴露中本地暴露,并画图辅助说明?

10、一般选择什么注册中心,还有别的选择吗?

1. Multicast 注册中心
2. zookeeper 注册中心

服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。 3. 基于 Redis 实现的注册中心 4. Simple 注册中心

11、dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?(面试高频题)

zookeeper的信息会缓存到本地作为一个缓存文件,并且转换成properties对象

12、项目中有使用过多线程吗?有的话讲讲你在哪里用到了多线程?(面试高频题)

参考

public FailbackRegistry(URL url) {
    super(url);
    this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            // Check and connect to the registry
            try {
                retry();
            } catch (Throwable t) { // Defensive fault tolerance
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}

13、zookeeper的java客户端你使用过哪些?

ZkClient和Curator两种java客户

14、服务提供者能实现失效踢出是什么原理?(高频题)

创建临时节点

15、zookeeper的有哪些节点,他们有什么区别?讲一下应用场景。

持久节点:
    在节点创建后,就一直存在,直到有删除操作来主动清除这个节点,
    也就是说不会因为创建该节点的客户端会话失效而消失
持久顺序节点
临时节点:
    临时节点的生命周期和客户端会话绑定,也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉
临时顺序节点


在分布式系统中,我们常常需要知道某个机器是否可用,传统的开发中,可以通过Ping某个主机来实现,Ping得通说明对方是可用的,相反是不可用的,ZK 中我们让所有的机其都注册一个临时节点,我们判断一个机器是否可用,我们只需要判断这个节点在ZK中是否存在就可以了,不需要直接去连接需要检查的机器,降低系统的复杂度

16、画一画服务注册与发现的流程图。

Service->Invoker->Exporter

本地暴露

远程暴露 f42fc241bac6340379fd51503cd5b122.png

fcf102b1284211d75380f8760063fc3f.png

17、在dubbo中,什么时候更新本地的zookeeper信息缓存文件?订阅zookeeper信息的整体过程是怎么样的?

b50a93ec543df0b8efeca4e9f8e5fb7c.png

1. 订阅节点变更事件 RegistryProtocol
public <T> Exporter<T> export(final Invoker<T> originInvoker)
        throws RpcException {
    ......
    // 订阅节点变更事件
    registry.subscribe(
        overrideSubscribeUrl, overrideSubscribeListener);
    ......
}
2. 发送订阅请求给服务器 FailbackRegistry
public void subscribe(URL url, NotifyListener listener) {
    super.subscribe(url, listener);
    removeFailedSubscribed(url, listener);
    try {
        // 发生订阅请求给服务器
        doSubscribe(url, listener);
    } catch (Exception e) {
        ......
        // Record a failed registration request to a failed list, retry regularly
        addFailedSubscribed(url, listener);
    }
}
3. 创建持久节点、设置节点监听回调 ZookeeperRegistry
protected void doSubscribe(
        final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            ......
        } else {
            List<URL> urls = new ArrayList<URL>();
            for (String path : toCategoriesPath(url)) {
                ConcurrentMap<NotifyListener, ChildListener>
                    listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url,
                        new ConcurrentHashMap<NotifyListener,
                            ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    // 设置监听回调
                    listeners.putIfAbsent(listener, new ChildListener() {
                        @Override
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                // 创建持久节点,path = /dubbo/com.alibaba.dubbo.demo.DemoService/configurators
                zkClient.create(path, false);
                // 设置path的监听
                List<String> children = zkClient.addChildListener(path, zkListener);
                if (children != null) {
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            // 更新缓存并判断是否重新进行暴露
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}
4. 更新缓存文件、判断是否需要重新暴露服务 FailbackRegistry
protected void notify(
        URL url, NotifyListener listener, List<URL> urls) {
    .....
    try {
        doNotify(url, listener, urls);
    } catch (Exception t) {
        .....
    }
}
5. AbstractRegistry
protected void notify(
        URL url, NotifyListener listener, List<URL> urls) {
    ......
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    // 解析URL中的category属性值:configurators
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
            String category =
                u.getParameter(
                        Constants.CATEGORY_KEY,
                        Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        // 更新本地的缓存文件
        saveProperties(url);
        // listener --> RegistryProtocol$OverrideListener
        listener.notify(categoryList);
    }
}
6. 更新本地的缓存文件 AbstractRegistry
private void saveProperties(URL url) {
    if (file == null) {
        return;
    }

    try {
        // 组装路径
        StringBuilder buf = new StringBuilder();
        Map<String, List<URL>> categoryNotified = notified.get(url);
        if (categoryNotified != null) {
            for (List<URL> us : categoryNotified.values()) {
                for (URL u : us) {
                    if (buf.length() > 0) {
                        buf.append(URL_SEPARATOR);
                    }
                    buf.append(u.toFullString());
                }
            }
        }
        properties.setProperty(url.getServiceKey(), buf.toString());
        long version = lastCacheChanged.incrementAndGet();
        if (syncSaveFile) {
            doSaveProperties(version);
        } else {
            // 线程池中执行文件的写入操作
            registryCacheExecutor.execute(new SaveProperties(version));
        }
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}
// 创建一个线程用于执行文件操作
private class SaveProperties implements Runnable {
    private long version;

    private SaveProperties(long version) {
        this.version = version;
    }

    @Override
    public void run() {
        doSaveProperties(version);
    }
}
7. 更新缓存文件 AbstractRegistry
public void doSaveProperties(long version) {
    if (version < lastCacheChanged.get()) {
        return;
    }
    if (file == null) {
        return;
    }
    // Save
    try {
        // 创建文件锁
        File lockfile = new File(file.getAbsolutePath() + ".lock");
        if (!lockfile.exists()) {
            lockfile.createNewFile();
        }
        RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
        try {
            FileChannel channel = raf.getChannel();
            try {
                // 尝试获取文件锁
                FileLock lock = channel.tryLock();
                if (lock == null) {
                    throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");
                }
                // Save
                try {
                    if (!file.exists()) {
                        file.createNewFile(); // 创建文件
                    }
                    FileOutputStream outputFile = new FileOutputStream(file);
                    try {
                        // 文件中写入内容
                        properties.store(outputFile, "Dubbo Registry Cache");
                    } finally {
                        outputFile.close();
                    }
                } finally {
                    lock.release();
                }
            } finally {
                channel.close();
            }
        } finally {
            raf.close();
        }
    } catch (Throwable e) {
        ......
    }
}
8. 比对之前路径判断是否需要重新暴露服务 RegistryProtocol
public synchronized void notify(List<URL> urls) {
    logger.debug("original override urls: " + urls);
    List<URL> matchedUrls = getMatchedUrls(urls, subscribeUrl);
    logger.debug("subscribe url: " + subscribeUrl + ", override urls: " + matchedUrls);
    // No matching results
    if (matchedUrls.isEmpty()) {
        return;
    }

    List<Configurator> configurators = RegistryDirectory.toConfigurators(matchedUrls);

    final Invoker<?> invoker;
    if (originInvoker instanceof InvokerDelegete) {
        invoker = ((InvokerDelegete<?>) originInvoker).getInvoker();
    } else {
        invoker = originInvoker;
    }
    //The origin invoker
    URL originUrl = RegistryProtocol.this.getProviderUrl(invoker);
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper<?> exporter = bounds.get(key);
    if (exporter == null) {
        logger.warn(new IllegalStateException("error state, exporter should not be null"));
        return;
    }
    //The current, may have been merged many times
    URL currentUrl = exporter.getInvoker().getUrl();
    //Merged with this configuration
    URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
    // 判断URL是否改变,如果改变则进行重新暴露服务
    if (!currentUrl.equals(newUrl)) {
        // 重新暴露服务
        RegistryProtocol.this.doChangeLocalExport(originInvoker, newUrl);
        logger.info("exported provider url changed, origin url: " + originUrl + ", old export url: " + currentUrl + ", new export url: " + newUrl);
    }
}

18、谈一下你们项目架构设计(很多人在回答这个的时候都容易回答SSH或者SSM,注意,所谓是SSH这些是技术选型,不是架构的设计)

19、既然你们项目用到了dubbo,那你讲讲你们是怎么通过dubbo实现服务降级的,降级的方式有哪些,又有什么区别?

dubbo中的服务降级
    屏蔽(mock=force)
    mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

    容错(mock=fail)
    还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

MockClusterInvoker
public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    // 获取配置中的mock属性值
    String value =
        directory
            .getUrl()
            .getMethodParameter(
                invocation.getMethodName(),
                Constants.MOCK_KEY,
                Boolean.FALSE.toString())
            .trim();
    // 未配置mock直接调用服务
    if (value.length() == 0 || value.equalsIgnoreCase("false")) {
        //no mock
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        // 屏蔽,不调用服务,直接返回配置的值
        if (logger.isWarnEnabled()) {
            logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
        }
        //force:direct mock
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        // 先调用服务,失败了直接返回配置的值
        try {
            result = this.invoker.invoke(invocation);
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                }
                result = doMockInvoke(invocation, e);
            }
        }
    }
    return result;
}

private Result doMockInvoke(Invocation invocation, RpcException e) {
    Result result = null;
    Invoker<T> minvoker;

    List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
    if (mockInvokers == null || mockInvokers.isEmpty()) {
        minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
    } else {
        minvoker = mockInvokers.get(0);
    }
    try {
        // MockInvoker.invoke
        result = minvoker.invoke(invocation);
    } catch (RpcException me) {
        ......
    } catch (Throwable me) {
        throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
    }
    return result;
}
MockInvoker
public Result invoke(Invocation invocation) throws RpcException {
    String mock = getUrl()
        .getParameter(
            invocation.getMethodName() + "." + Constants.MOCK_KEY);
    if (invocation instanceof RpcInvocation) {
        ((RpcInvocation) invocation).setInvoker(this);
    }
    if (StringUtils.isBlank(mock)) {
        // 获取mock的配置值
        mock = getUrl().getParameter(Constants.MOCK_KEY);
    }

    if (StringUtils.isBlank(mock)) {
        throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
    }
    mock = normallizeMock(URL.decode(mock));
    if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {
        RpcResult result = new RpcResult();
        result.setValue(null);
        return result;
    } else if (mock.startsWith(Constants.RETURN_PREFIX)) {
        mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
        mock = mock.replace('`', '"');
        try {
            Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
            Object value = parseMockValue(mock, returnTypes);
            return new RpcResult(value);
        } catch (Exception ew) {
            throw new RpcException("mock return invoke error. method :" + invocation.getMethodName() + ", mock:" + mock + ", url: " + url, ew);
        }
    } else if (mock.startsWith(Constants.THROW_PREFIX)) {
        mock = mock.substring(Constants.THROW_PREFIX.length()).trim();
        mock = mock.replace('`', '"');
        if (StringUtils.isBlank(mock)) {
            throw new RpcException(" mocked exception for Service degradation. ");
        } else { // user customized class
            Throwable t = getThrowable(mock);
            throw new RpcException(RpcException.BIZ_EXCEPTION, t);
        }
    } else { //impl mock
        try {
            Invoker<T> invoker = getInvoker(mock);
            return invoker.invoke(invocation);
        } catch (Throwable t) {
            throw new RpcException("Failed to create mock implemention class " + mock, t);
        }
    }
}

20、dubbo监控平台能够动态改变接口的一些设置,其原理是怎样的?

21、既然你说你看过dubbo源码,那讲一下有没有遇到过什么坑?(区分度高,也是检验是否看过源码的试金石)

22、dubbo的原理是怎么样的?请简单谈谈

23、有没有考虑过自己实现一个类似dubbo的RPC框架,如果有,请问你会如果着手实现?(面试高频题,区分度高)

24、你说你用过mybatis,那你知道Mapper接口的原理吗?(如果回答得不错,并且提到动态代理这个关键词会继续往下问,那这个动态代理又是如何通过依赖注入到Mapper接口的呢?)

25、描述一下dubbo服务引用的过程,原理

26、既然你提到了dubbo的服务引用中封装通信细节是用到了动态代理,那请问创建动态代理常用的方式有哪些,他们又有什么区别?dubbo中用的是哪一种?(高频题)

JDK的动态代理代理的对象必须要实现一个接口。
CGLIB对指定的目标类生成一个子类,并覆盖其中方法实现增强,但由于采用的是继承,所以不能对final修饰的类进行代理

27、除了JDK动态代理和CGLIB动态代理外,还知不知道其他实现代理的方式?(区分度高)

javassist生成字节码来实现代理

28、你是否了解spi,讲一讲什么是spi,为什么要使用spi?

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。
这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能

1. 定义接口
public interface Robot {
    void sayHello();
}
2. 定义接口实现类
public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}
3. 在 META-INF/services 文件夹下创建一个文件,文件名为接口全限定名,文件内容为实现类的全限定的类名
        META-INF/services/org.apache.spi.Robot
        org.apache.spi.OptimusPrime
        org.apache.spi.Bumblebee

29、对类加载机制了解吗,说一下什么是双亲委托模式,他有什么弊端,这个弊端有没有什么我们熟悉的案例,解决这个弊端的原理又是怎么样的?

30、既然你对spi有一定了解,那么dubbo的spi和jdk的spi有区别吗?有的话,究竟有什么区别?

1. 使用缓存提升了性能
public static <T> ExtensionLoader<T> getExtensionLoader(
            Class<T> type) {
    if (type == null) //拓展点类型非空判断
        throw new IllegalArgumentException("Extension type == null");
    if(!type.isInterface()) { //拓展点类型只能是接口
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(!withExtensionAnnotation(type)) { //需要添加spi注解,否则抛异常
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //从缓存EXTENSION_LOADERS中获取,如果不存在则新建后加入缓存
    //对于每一个拓展,都会有且只有一个ExtensionLoader与其对应
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(
                type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
2. JDK的spi要用for循环, 然后if判断才能获取到指定的spi对象, dubbo用指定的key就可以获取spi对象
public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) { // 返回默认的值
        return getDefaultExtension();
    }
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    // 使用双重检查锁的单例设计模式
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

public class Holder<T> {
    private volatile T value; // volatile
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}

public Class<?> compile(String code, ClassLoader classLoader) {
    Compiler compiler;
    ExtensionLoader<Compiler> loader =
        ExtensionLoader.getExtensionLoader(Compiler.class);
    String name = DEFAULT_COMPILER; // copy reference
    if (name != null && name.length() > 0) {
        // 通过key值获取SPI对象
        compiler = loader.getExtension(name);
    } else {
        // 获取默认的SPI对象
        compiler = loader.getDefaultExtension();
    }
    return compiler.compile(code, classLoader);
}
3. JDK的spi不支持默认值,dubbo增加了默认值的设计
/**
 * Return default extension, return <code>null</code> if it's not configured.
 */
public T getDefaultExtension() {
    getExtensionClasses();
    if (null == cachedDefaultName || cachedDefaultName.length() == 0
            || "true".equals(cachedDefaultName)) {
        return null;
    }
    return getExtension(cachedDefaultName);
}

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
4. spi增加了IoC、AOP
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader =
            ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //factories=[SpiExtensionFactory,SpringExtensionFactory]
        //遍历获取spi,先从SpiExtensionFactory获取,如果没有, 再从SpringExtensionFactory获取
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

31、你提到了dubbo中spi也增加了IoC,那你先讲讲Spring的IoC,然后再讲讲dubbo里面又是怎么做的?

Spring的IoC

    Spring的IoC容器主要有两种, 即BeanFactory和ApplicationContext
    1. BeanFactory是Spring中最底层的接口, 只提供了最简单的IoC功能, 负责配置, 创建和管理bean。
    2. ApplicationContext继承了BeanFactory, 拥有了基本的IoC功能外, 还支持: 国际化、消息机制、统一的资源加载、AOP
    3. ApplicationContext在加载的时候就会创建所有的bean,BeanFactory需要手动调用getBean方法才会创建bean

899553278301a56b94fc96ef42b47f85.png

32、你提到了dubbo中spi也增加了AOP,那你讲讲这用到了什么设计模式,dubbo又是如何做的?

⚠️ **GitHub.com Fallback** ⚠️