距离上一次更新日志已经差不多有两周了,因为马上要回学校考试了,所以再更一波。
准确来说这两周并没有什么任务可做,所以都属于自己的学习阶段,那么问题来了,两周都学了点什么呢?
闲来蛋疼倒是看了两周的Dubbo,大致的实现都看了点吧,服务的发布调用过程等等。但是在这里让我讲讲具体如何实现我已记不大清楚了,只能说说读过部分源码后自己的感受。
1.缓存
在Dubbo的设计中有很多地方都出现了缓存,而且是各种缓存。下面看代码:
private final ConcurrentMap这些代码都是ExtensionLoader中的成员变量,都是一些缓存的类或者名字等等。那么问题来了,为什么需要缓存?, String> cachedNames = new ConcurrentHashMap Class>>> cachedClasses = new Holder, String>(); private final Holder
在整个架构之中缓存有着举足轻重的感觉,就那ExtensionLoader来说,每一个接口只会有一个对应的ExtensionLoader,所以这个对象就都被以
2.扩展性
一个灵活的框架都应该在扩展性上提供相当的支持,当然Dubbo也不例外。Dubbo的扩展非常简单,框架可以像对到对待自己的组件一样对待扩展组件,而且扩展也非常简单。Dubbo的扩展时基于SPI机制的,看代码:
@SPI(AllDispatcher.NAME) "all"
public interface Dispatcher {
}
这种类似的代码在Dubbo中随处可见,我们重点关注类名称上面的注解@SPI,这就意味着我们的Dispatcher的默认实现就是去找all了,但是去哪里找呢?
答案就是到MET'A-INF下去找名为com.alibaba.dubbo.remoting.Dispather的文件,因为Dispatcher的全名就是这个,所以在META-INF下找到这个文件后,发现里面记录着有这么一项:all=com.alibaba.dubbo.remoting.transport.dispatcher.all.AllDispatcher,所以我们就知道这个接口的默认实现就是这个AllDispatcher喽。AllDispatcher就是用来处理线程的线程分派器,当服务提供段收到消费端的调用的时候,就全部把这些线程交给线程池去处理。
这种机制相对于原生的SPI机制有一点差别,不过差异不大。原生的SPI只根据key值就可以确定默认实现,而Dubbo中的SPI是根据key和value组合来确定具体哪一个是具体实现。
这样的话在我们调整实现的时候也就是在Dubbo的配置文件中配置
3.安全性
这Dubbo的源码中充斥着线程的处理,我感触最深就是对于单例模式的运用,在单例的模式中又能充分考虑线程的安全性。看代码:
/** * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}. * * @param name * @return */ @SuppressWarnings("unchecked") public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } //先从缓存中去取 Holder重点关注的代码我已经用红色标出来了,我们看这个单例模式的运用,分别两次判断instance ==null,双重判断来保证及时在多线程的环境下也不会多创建一个instance对象,因为instance的创建过程非常耗时而且消耗资源,所以在Dubbo就大面积出现这种类似的模式来避免多次创建对象。可谓是设计的非常精良,在很细微的地方都在兼顾性能与安全性。holder = cachedInstances.get(name); if (holder == null) { //如果缓存中没有的话在创建一个然后放进去,但是此时并没有实际内容,只有一个空的容器Holder cachedInstances.putIfAbsent(name, new Holder if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { //根据名字创建特定的扩展 instance = createExtension(name); holder.set(instance); } } } return (T) instance; }()); holder = cachedInstances.get(name); } Object instance = holder.get();
4.模块化
模块化这个问题是我昨天才感觉到的,以为组内正在做相关的服务化功能,所以我也看了看组内的代码。但是我并没有去关注具体某个接口如何实现,而是从架构上看看这个内容都怎么布局,相对于平时的代码都有哪些区别。
~依赖问题,服务化时候自己内部的模块只能依赖自己内部的模块,不能对其他服务有很大的依赖,因为这样的话假如对方的服务挂掉的话自己岂不是受影响了,所以很大程度上要减少自己的依赖问题。
~流量问题,因为我们是要提供服务的,所以一定要考虑流量问题。如果A调用我们的服务的话需要取到一个Person{name,age}对象,而我们返回Person{name,age,gender}就是不好的选择,因为有冗余项,所以在这里我们就要保证字段的有效性,一定是满足对方的需求但是尽力减少冗余的,以为一旦有冗余的话就意味着流量的增加,当服务的调用非常多的时候这种情况带来的影响就非常大了,所以在设计的时候就要考虑这个问题。
~代码的优雅,无论是Dubbo还是组内的代码都是优雅,不会出现让你瞠目结舌的渣渣代码,而且凡是容易模糊的地方都有良好的注释,这让我学习的过程中减少了很多困难。
~模块问题,Dubbo的模块分的非常清晰,dubbo-cluster,dubbo-filter,dubbo-rpc等等模块都非常具有描述性,几乎我们只要看到名字就能猜到具体的内容了,然后定位的时候就非常容易,不仅如此我们在模块内部的小模块也能看到非常清晰的结构:我们以dubbo-registry包来看,包下面还有这些包:dubbo-registry-api,dubbo-registry-default,dubbo-registry-multicast,dubbo-registry-redis,dubbo-registry-zookeeper。api包就是共有的描述接口,也就是供其他包调用的接口,里面的很多接口的实现就是基于后面的几个包的,而后面的包下的内容我相信一眼就能明白了吧,设计非常的精良。
5.设计模式
Dubbo中运用了很多设计模式,其中用的比较多的就是代理,装饰者模式,策略模式,门面模式等等。
代理模式:在消费者发起调用的时候就会在消费者端创建一个消费者的代理对象,然后通过这个代理对象去进行后期的对于Invoker的处理,这种代理模式在Dubbo的还有很多,我只是举了其中一部分例子。
装饰者模式:这点在Dubbo的服务提供端和服务消费端你的初始化过程中出现了非常多,看代码:
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); }代码的意思非常简单吧,但是如果追进去的话就发现了这一层层的嵌套逻辑其实挺恶心的。里面层层的包装,层层的初始化就是运用了装饰者模式。
门面模式:门面模式往往写成Facade,但是这里并没有这么写。
server = Exchangers.bind(url, requestHandler);
这就是Dubbo中的一句代码,目的是创建一个Server对象,但是我们看到的是Exchangers.bind(),其实这个Exchangers就是一个门面而已,里面封装的还是Exchanger对象,装而调用Exchanger的bind方法,这么做的好处当然就是保持暴露的局部性,只暴露自己想暴露的接口出来。
策略模式:Dubbo中对于层层传递的消息对象统一采用了URL对象,也就是说我们层层传输的参数都会封装成URL的格式,然后在我们需要这些参数的时候就直接从相关的URL上取到,这么做的好处就是统一性,我们不用担心我们从A层取B层传递内容的时候取到的是AB,而从B层取到C层的内容的时候取到的BC这种问题。统一的采用URL策略去处理的优点就在这里。
当然了Dubbo还有很多值得学习的地方,这里暂且就这么着吧。
期末考完试回来了继续更新。