共计 3315 个字符,预计需要花费 9 分钟才能阅读完成。
一、SOA 模式
首先简单介绍一下 SOA 模式,这对我们后面理解 Dubbo 很有帮助。
SOA 模式是什么?
SQA(Service-Oriented Architecture)即面向服务架构,它将应用程序的不同功能单元(这里就理解为服务)进行了拆分。在这种架构下项目不会直接和数据库进行交互,而是通过调用不同服务的接口来访问数据库。
模式优点在哪?
这样最直接的好处就是解决代码冗余,如果多个项目同时都要访问数据库同一张表。比如用户表的访问。我们可以直接调用用户服务里面的接口进行开发,而不需要每个项目都去写一遍用户表的增删改查。除了这个,SOA 能带给我们的好处就是能够让开发者以更迅速、更可靠、更具重用性架构整个业务系统。较之以往 MVC 开发模式,以 SOA 架构的系统能够更加从容地面对业务的急剧变化。
SOA 示意图:
二、Dubbo 基本组成
聊完了 SOA,现在来看看 Dubbo 内部架构,相信大家多多少少看过下面这幅图:
@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {// 根据 URL 得到服务接口为 *,也就是所有
String root = toRootPath();
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);// 拿取 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) {for (String child : currentChilds) {child = URL.decode(child);
if (!anyServices.contains(child)) {anyServices.add(child);
// 如果 consumer 的 interface 为 *,会订阅每一个 url,会触发另一个分支的逻辑
// 这里是用来对 /dubbo 下面提供者新增时的回调,相当于增量
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
});
zkListener = listeners.get(listener);
}
zkClient.create(root, false);
// 添加监听器会返回子节点集合
List<String> services = zkClient.addChildListener(root, zkListener);// 订阅 root 目录下的子元素,比如:/dubbo/com.learnDubbo.demo.DemoService/providers
if (services != null && !services.isEmpty()) {for (String service : services) {service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {
// 这边是针对明确 interface 的订阅逻辑
List<URL> urls = new ArrayList<URL>();
// 针对每种 category 路径进行监听
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);
}
// 创建节点
zkClient.create(path, false);
// 增加回调
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 并且会对订阅的 URL 下的服务进行监听,并会实时的更新 Consumer 中的 invoke 列表,使得能够进行调用。这个方法不展开讲
notify(url, listener, urls);
}
} catch (Throwable e) {throw new RpcException("Failed to subscribe" + url + "to zookeeper" + getUrl() + ", cause:" + e.getMessage(), e);
}
}
4、invoke:根据获取到的 Provider 地址,真实调用 Provider 中功能。这里就是唯一一个同步的方法,因为消费者要得到生产者传来的数据才能进行下一步操作,但是 Dubbo 是一个 RPC 框架,RPC 的核心就在于只能知道接口不能知道内部具体实现。所以在 Consumer 方使用了代理设计模式,创建一个 Provider 方类的一个代理对象,通过代理对象获取 Provider 中真实功能,起到保护 Provider 真实功能的作用。
invoke 部分源码分析
有兴趣的可以看看 invoke 调用过程
5、Monitor:Consumer 和 Provider 每隔 1 分钟向 Monitor 发送统计信息, 统计信息包含, 访问次数, 频率等
四、总结
这里只是稍微对 Dubbo 的原理做了一下分析,想要弄懂 Dubbo 还需要结合源码来看。尽管很多时候我们只是一个使用者,但是能够理解内部运行原理不但能够让我们更好的使用,同时里面的编程思路,设计模式也是我们需要学习的。后面还会做更多关于 Dubbo 的原理解析。