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
6、dubbo都有哪些协议,他们之间有什么特点,缺省值是什么?
- dubbo支持多种协议, 默认使用的是dubbo协议, Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况, 反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低
- RMI 协议采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式。
- Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
- 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现
- 基于 WebService 的远程调用协议,基于 Apache CXF 的 frontend-simple 和 transports-http 实现。 可以和原生 WebService 服务互操作,即: 提供者用 Dubbo 的 WebService 协议暴露服务,消费者直接用标准 WebService 接口调用, 或者提供方用标准 WebService 暴露服务,消费方用 Dubbo 的 WebService 协议调用。
- dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展, 在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等
- 基于 memcached 实现的 RPC 协议
- 基于 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,端口等信息暴露给远程客户端,调用时需要网络通信。
本地服务的暴露
- 具体服务到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
本地暴露
远程暴露
17、在dubbo中,什么时候更新本地的zookeeper信息缓存文件?订阅zookeeper信息的整体过程是怎么样的?
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
32、你提到了dubbo中spi也增加了AOP,那你讲讲这用到了什么设计模式,dubbo又是如何做的?