- 浏览: 99981 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (50)
- druid (1)
- java (7)
- database (1)
- 源码 (6)
- dubbo (4)
- jetty (1)
- protocol (1)
- 工作流 (3)
- 电商 (1)
- 支付平台 (1)
- 大数据 (0)
- 微信小程序 (1)
- 短信平台 (1)
- jms (1)
- jndi (1)
- spi (1)
- FileUpload (0)
- concurrent (1)
- 统计业务 (0)
- 业务 (3)
- sql (2)
- andriod (1)
- maven (1)
- OAuth (1)
- ws (1)
- spring (6)
- mybatis (1)
- java rocketmq (0)
- rocketmq (1)
- RxJava (1)
- java模式 (1)
- AI (0)
- 机器学习 (1)
- springboot (1)
- tomcat (1)
- 协议 (1)
- springcloud (1)
- stream (1)
- 技术管理 (1)
- k8s (1)
- ser (0)
- istio (1)
- ddd (1)
- 微服务 (1)
- 操作笔记 (1)
最新评论
-
herman_liu76:
luozhen89 写道讲得很好。知识后面的KAFKA跟OAu ...
尽量把OAuth2.0的原理讲透透的 -
luozhen89:
讲得很好。知识后面的KAFKA跟OAuth有什么关系,没看懂。 ...
尽量把OAuth2.0的原理讲透透的 -
herman_liu76:
ZHENFENGSHISAN 写道太累了啊,哥唉~ 我也觉得很 ...
代码快看哭了-吐槽与感悟汇总 -
ZHENFENGSHISAN:
太累了啊,哥
代码快看哭了-吐槽与感悟汇总 -
herman_liu76:
1126481146 写道厉害啊,有联系方式吗,学习学习,我现 ...
druid 源码分析与学习(含详细监控设计思路的彩蛋)
Dubbo是阿里巴巴公司实现SOA治理的工具,最近听到有朋友公司用这个Dubbo,正好想了解一下源码。经过一小段时间分析,发现知识点非常多,很有价值。包括:动态代理,spring整合,各种设计模式,线程池,锁,netty这样基于nio的tcp框架,协议的设计,当然最重要的是一种解决问题的思路,就吧这些体会记录下来:
网上已经看到一些源码分析,总感觉缺少点什么,看完文章还是感觉云里雾里的,可能是自己水平不够吧,所以想从其它角度,先少用代码,重点介绍理念与方案,用自己的话写出来,不一定正确,欢迎指导!!!
许多资料书只是工具书,只有在源码的综合使用过程中才能有更深的体会,才能碰到问题时,有直觉上的方案!
也许已经写过很多很多代码,真正有价值的代码真的多吗?Dubbo源码确实有助于提高整体水平!
一、Dubbo要干什么?
远程跨应用调用。就是这边有接口,那边有实现。那么这边产生一个代理对象(即:Proxy.newProxyInstance)实现接口,对于任何请求,都把请求的东西(主要有方法名,参数类型,参数值)发出去。那边的应用会根据一些信息,找到一个exporter,而它持有一个invoker(即:new AbstractProxyInvoker,这个不是通常生成动态代理的标准invoker,而且它的方法invoke()也可以换成其它名字。),因为它持有真正的接口实现类,所以再根据方法名字反射得到值,再返回给请求方。
二、知识点
1.动态代理
比如jdk动态代理,这个资料很多。简单的使用,就是有一个接口,写一个invoker,持有实现类。再通过Proxy.newProxyInstance返回一个代理对象,它实现了所有的接口,而所有的调用都变成invoke,并传来方法与参数。这时再对持有的实现类反射调用就可以了。在反射调用前后可以做点什么,这点很重要(事务啊,计时啊,推动工作流啊,读缓存啊.....)!!
2.层层包装
这算设计模式了。主要用在Dubbo中的各个handler之间。举个现实栗子,公司门口有人送信来,首先门卫那到,它有一个处理方式,如果是垃圾信就丢掉。重要的交给收发人,同时把送信人告诉收发人。收发人看到是简单问候,直接就让送信人回复了,如果是重要的信,再交给领导,同时把送信人信息告诉领导。领导拿到后,进行处理,结果直接给送信人,这样就处理完了。如果有一天要与歪果人打交道,在门卫收信前设置一个翻译人,他收到,如果是中文就给门卫,如果是外文,先翻译再给门卫,当然翻译接触送信了,所以这时由他把送信人信息给门卫。每个人都有一个handle方法,每个人除了领导都引用下一个处理人,同时把送信人信息交给对方。 有句话说,人的本质是各种社会关系的总和,感觉在面向对象的编程中,对象的本质是各种引用关系的总和。
3.内部类与模板方式
一般抽象类中写公共方法,不同的了类中写不同的方法。如果一个方法中有一部分相同,一部分不同,那这个方法就拆成2个方法,不同的部分成为抽象方法在子类中实现。有时候这个不同,只有在使用时都知道有什么不同,那干脆就只在new这个抽象类时补充上不同的那个抽象方法,有点象匿名类(new AbstractProxyInvoker的时候就是这样。见JdkProxyFactory类。)。有时候这个不同的部分包装成实现通用接口的对象,那就是回调了,比如hibernateTemplate中经常用的。看情况选用吧。
4.从具体到抽象
通常一个大的应用中会用到已有的东东。为了灵活性,会使用不同的已发明的轮子,但轮子尺寸不一,咋办?包装啊。比如说:我要把消息给远方的朋友,我可以打电话,可以发email,可以QQ,可以写信。利用的都是已经有的通讯方式,不过感觉麻烦,我只想说一句话啊。那干脆雇佣几个人,一个会写信,一个会打电话,一个会email,那我只要选择其中一个人(好象是自动发现机制吧),对他们说句话,就OK了,其它事情他们搞定。Dubbo可以用netty,也可以用其它方式作为transport的工具,反正都是已有的轮子,包装起来用。Dubbo会有NettyClient,GrizzlyClient...反正他们都实现通用的方法。
5.netty通讯
netty是基于nio的通讯框架。基本原理不说了,重点体会NettyClient、NettyServer、NettyHandler、NettyChannel、以及和ChannelHandler的关系。NettyClient的两面性特别注意。NettyHandler构建的时候把NettyClient当成ChannelHandler来用,因为它持有ChannelHandler,而它本来是个client。
NettyHandler可以监控任何nio通讯事件,监控到了,它就让引用的ChannelHandler(NettyClient)来处理事情,并把【来信通道】告诉处理的人。而处理的人就象前面介绍的门卫、收发人,领导那样依次处理。如果是要回复的处理(twoway),就直接用【来信通道】把最后的处理结果返回就OK了。
从命名看功能:client一般有close(),connect(),reset()等方法,Channel一般有send()等功能,ChannelHandler一般是利用Channel来做些事情。
6. HeaderExchanger
当把客户端的调用信息,远程发送给服务器时,当然需要netty这样的通讯工具来帮忙。比如我们经常要让远方的朋友帮忙做事时,有时候让邮局送信,有时候让快递送信,有时候让镖局送东西。太麻烦了,我干脆在自己这边成立一个送信办事处,朋友那边成产一个办事处。办事处之间定时通讯一下(心跳),看着通讯有效不。服务点开张的时候,默认选择一个邮局(nettyclient)来用。办事处的通讯也有一些功能象client,也有一些功能象channel。所以就分成HeaderExchangeClient与HeaderExchangeChannel吧。办事处不象邮局那样处理各种各样的东东,我们自己的办事处只处理自己的request与response。主要的包括心跳请求,办事请求与相应的响应,还设计请求的同步与异步,同步时请求线程进入wait。这些格式就自己来设计了。
7.netty的encode/decode
前面讲netty后,缺少了一个重要步骤。netty传输时,读写都是对ChannelBuffer操作,就算是临时仓库吧。writeBytes、readBytes是ChannelBuffer的功能。得到的byte[]需要相互转换request与response。设计好的request转成byte[]是这样的,【头部】一共16位byte。01是叫magic码,2.3是请求标识或者twoway之类的,4-11是请求的ID,12到15是记录后面的数据的长度。response头部也是magic码,2是事件标识,3是响应码,其它不一一道来了。这样,来了请求或者响应,可以组成一堆byte[]放仓库,当仓库里来了一堆byte[],也可以分析出是啥东东。也有分析不出来的,返回DecodeResult.NEED_MORE_INPUT这个枚举对象。当你用netty实现httpserver时,你可以用现成的decode得到一个httprequest对象。
8 重要的对象关系
HeaderExchangeClient拥有一个NettyClient,还拥有一个HeaderExchangeChannel。但HeaderExchangeChannel做的事情还是找NettyClient来做,还记得前面说过NettyClient的两面性,即象client,又象channel。
NettyClient更厉害了,它除了是client外,因为持有chanel(最核心的netty里的chanel),可以直接用chanel发送东西出去。构造的时候因为持有外部传给它的ChannelHandler,本来已经包装过了(new DecodeHandler(new HeaderExchangeHandler(handler))),它还再包装了一下(wrapChannelHandler(url, handler)),所以可以处理nettyhandle中的通道事务。nettyhandle是NettyClient生成的用来守着通道的,告诉它,有情况来找我,因为我持有ChannelHandler,我也可以变身ChannelHandler。
9 重要的处理过程
层层包装的ChannelHandler处理channel事务,包装的同时还传递着channel供其使用。从这两句看的出来:new DecodeHandler(new HeaderExchangeHandler(handler) 与new MultiMessageHandler(new HeartbeatHandler(....)。最原始的Handler是DubboProtocol中的ExchangeHandlerAdapter的匿名子类,在它外面一直在包装。等到了nettyhandle用NettyClient来处理的时候,要一层一层的从外到里处理了。
比如:HeartbeatHandler处理时,发现是心跳请求,直接产生一个心跳回复就OK了,用channel发出去。HeartbeatHandler不会再传递给内部了。如果不是心跳请求,传给HeaderExchangeHandler处理,并把channel给它。如果HeaderExchangeHandler发现是响应,它要把响应结果放好,并唤醒请求时等待的线程,说:返回值来了,醒醒,该干活了。如果发现是真正的请求,HeaderExchangeHandler还要把channel和请求转给DubboProtocol中最原始的Handler来处理。原始的Handler就不传递了,调用reply()来找到exporter,再找到invoker,处理后返回值。
10 java的SPI机制及dubbo扩展,及为啥扩展
前面就提到自动发现ExtensionLoader.getExtensionLoader(...)这样的语句,使用的是SPI机制。SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。这么说吧,一般都是对接口编程,具体的实现类不同可以实现不同的功能。如果实现类让别人扩展,那只要告诉我实现此接口的类在哪?找到就可以实例化。我司的一个应用中,把实现类放在数据库中也达到此目标。SPI原理就是就放在META-INF\services里面就可以找到了,不足之处是把所有的实现都给你了。你也许只想用某个特殊的实现类呢?
dubbo先实现了一个此接口的Adaptive类,实现的的方法是用代码写一个java文件(createAdaptiveExtensionClassCode()),再用代码编译(compiler.compile(code, classLoader)),再加载进来。这个Adpative类实现了接口的所有标注了adaptive的方法。当调用这个adaptive类的方法时,这个daptive类从方法的参数中提取一个重要的值,根据这个值再调用真正的实现类。这简直是灵活性++啊,当然难度^2啊。
为啥这么做呢?还是先举个栗子,有人送东西来你家,你家好几个人,不知道谁来收。送东西的人说收货人信息封在里面,不能拆。怎么办,要么都过来拿,不是自己的东西的人就白走一趟。要么找个代理人,他可以收任何人的东西,收到后拆开,根据里面的真正的收货人,叫某某来拿。这个代理人就是daptive类。你可以直接定义这个代理人,情况比较多时,恩恩,这里存在某种看似重复的东东,那可以动态生成这个代理人嘛。为啥送货人不能直接看到关键的收货人信息呢?这么做简直就是.....是炫耀技术。
11 线程池
前面提到了心跳,心跳是一种定时执行的任务,在HeaderExchangeClient中就有ScheduledThreadPool来不断发送心跳请求。java中有四种线程池,这个找资料都都搞明白。包括什么核心线程数,最大线程数,无界队列什么的...
不过这里有几个值得学习的地方:首先,HeartBeatTask中有一个ChannelProvider接口定义,还持有实现这个接口的对象,构造Task时传入的。当要new HeartBeatTask放入线程池时,new的同时,实现这个类中接口的匿名类。高手们写代码就是高深。但想想为啥这么写呢?定时任务主要的功能是把心跳消息从各个通道发出去。提供通道他并不关心,最好由外部来实现,作为匿名类传进来就好了,省得绑定太密切,影响独立性。其实,ScheduledFuture作为启动线程池返回的对象,可以比如好的结束定时任务线程池heatbeatTimer.cancel(true);以前没怎么用过。
12 同步锁 ReentrantLock
synchronized是比较常用的线程同步用法,要求高点就用concurrent里的东东。ConcurrentHashMap是多线程中用的比较多的容器,线程同步并且效率高。AtomicLong用来对request计数,使用cpu的什么cas保证线程安全。dubbo在请求后,就马上去取值,没有值就wait(),这里面有一个超时时间,就要用到ReentrantLock.newCondition()的这样的条件了。一旦返回值来了,就done.signal();通知等待的线程来读数据了。普通的wait(),notify()功能太少了吧。顺便提一下ChannelHandlerDispatcher中的CopyOnWriteArraySet吧,读写分享的思想,读不加锁,而且读应该远超过写的情况。如果写的开销比较大,不分开的话,要么读也加锁保证一致性,如果不加,读的会很混乱。写肯定要加锁,写的时候写复制出的东东,利用空间换时间。
三、把代码把知识点串起来
下面开始把客户端产生代理,到得到返回值的整个过程,用代码串起来,当然上面的知识点也都串了起来,开始撸~
1. 配置到spring中,利用spring解析xml配置时初始化整个过程。
spring加载配置后执行afterPropertiesSet,就从
com.alibaba.dubbo.config.spring.ReferenceBean的afterPropertiesSet()开始跟踪。
-->getObject()--->get();--->init();-->createProxy(map)--->invoker = refprotocol.refer(interfaceClass, urls.get(0));--->return (T) proxyFactory.getProxy(invoker);
最后一个就是得到客户端的服务代理,重点是refprotocol.refer(interfaceClass, urls.get(0))方法得到invoker。下面直接到com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol看 refer(Class<T> serviceType, URL url) 方法。里面有个new DubboInvoker<T>(serviceType, url, getClients(url), invokers);方法。
2.产生invoker前做了很多事情
注意:先看getClients(url)参数,--->initClient(url);---->Exchangers.connect(url ,requestHandler);(requestHandler是重要的处理武器,后面会被层层包装的,前面知识点有提到)---->getExchanger(url).connect(url, handler);(这里要找一个exchanger,再用connect方法)---(自动发现机制?)->HeaderExchanger.connect(URL url, ExchangeHandler handler)---->new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));(看到了吧,出现了HeaderExchangeClient,它还持有一个传输者,还把那个处理武器包装后交给了它,后面继续找Transporters)--->getTransporter().connect(url, handler);--(自动发现机制?)->NettyTransporter.connect(URL url, ChannelHandler listener)---->new NettyClient(url, listener);---->NettyClient new的时候,在父类AbstractClient构造时--->doOpen();connect();
这下都清楚了吧,在产生DubboInvoker之前,生成了HeaderExchangeClient,它持有NettyClient,同时把原始武器requestHandler包装后最好交给NettyClient,NettyClient打开,并开始连接server了。这部分只是介绍了DubboInvoker生成中的其中一个参数。下面就可以生成DubboInvoker了。
3.invoker做什么事情?
DubboInvoker要用来生成代理对象的,所以它最重要的方法就是invoke(Invocation inv) ,这个方法在父类AbstractInvoker里。子类实现了个性化的doInvoke(final Invocation invocation)部分(模板模式)。参数为啥变成final了?哈哈,这个自己再找答案,这里不讲了。
doInvoke(final Invocation invocation) 中,对于同步请求,就执行return (Result) currentClient.request(inv, timeout).get();这句。
其中currentClient就是前面getClients(url)中得到的,姑且认为就是HeaderExchangeClient吧,它用来把inv(包括调用接口、方法、参数、值等信息)发出去,还设置了超时间,半天不返回就不要了。最后一个get(),就是从com.alibaba.dubbo.remoting.exchange.support.DefaultFuture中取返回值,这时候就使用了lock,取不到值就等待,等被唤醒了就isDone()--->returnFromResponse();--->return res.getResult();。终于拿到返回值了。
4.如何把请求发出去?
currentClient.request(inv, timeout)是上面过程中的最重要的一个方法了。
request(Object request, int timeout) ---> channel.request(request, timeout);--->channel.send(req);
详细说一下上面的:currentClient的请求,转交给channel(HeaderExchangeChannel)来办(channel从名字上就知道是干这事情的),channel对request处理了一下先,req.setData(request);就是new了一个新的req,把传过来的request当成了req的数据部分了,就是加了一个【头,又叫header】。新的请求对象已经组装完成,HeaderExchangeChannel接到任务后也要用它的channel发出去。一环套一环啊,自己的事情都不自己做,都找别人做,还好不开工资的。HeaderExchangeChannel拥有的channel是啥?前面知识点有介绍过就是nettyClient啊,这个具有两面性的东东。看来HeaderExchangeClient不仅拥有它,还把它给了HeaderExchangeChannel来使唤。
nettyClient自己也有nettyChannel,为啥HeaderExchangeChannel不直接引用呢?我们知道netty是一个整体。nettyClient统一收个口子,如果不用netty,只换一下client就行了。符合耦合度低,内聚度高的原则。
5.现在nettyCient如何把请求发出去?
nettyClient --->父类AbstractPeer的send(Object message)--->AbstractClient中的send(Object message, boolean sent)--->Channel channel = getChannel();再channel.send(message, sent); 其中的getChannel();中返回的是NettyChannel.getOrAddChannel(c, getUrl(), this);说明NettyChannel持有netty最核心的channel(org.jboss.netty.channel.Channel ),NettyChannel.send时用这句:ChannelFuture future = channel.write(message);--->write是这个核心channel的发信息的方法。
上面客户端的过程介绍完了,下面是服务器的介绍了。nettyClient发出去的,当然nettyServer要收。nettyClient当然也要收nettyServer返回的结果了。
6.现在nettyServer如何启动
在nettyServer收请求时,首先应该是服务器那边启动了,nettyServer监听各种请求。所以看看服务器启动过程。同样中服务端的spring启动开始。com.alibaba.dubbo.config.spring.ServiceBean.afterPropertiesSet()开始跟踪吧。--->export()--->doExport()--->doExportUrls();--->doExportUrlsFor1Protocol(protocolConfig, registryURLs);--->
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));--->protocol.export(invoker);--->exporters.add(exporter);
其中的proxyFactory.getInvoker就是new AbstractProxyInvoker<T>(proxy, type, url) ,同时实现里面的抽象方法doInvoke。当收到客户端的调用Invocation inv后,可以知道调用的方法名、参数类型,参数值,同时它持有proxy这个接口实现类。那剩下的就是简单的反射得到最后的结果了。invoker有了,下面是protocol.export(invoker);把invoker用协议暴露出来。那就看看com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol中的export方法。
export中主要做了两件事。DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);与openServer(url);。前面是exporter持有invoker并放入一个exporterMap中存下来。后面继续跟踪---->createServer(url)--->server = Exchangers.bind(url, requestHandler);
还记得client时,用的是Exchangers.connect生成HeaderExchangeClient吗?是了,这里用Exchangers.bind生成服务端,记着这里也传入了requestHandler这个武器,用来处理request的。继续跟踪--->getExchanger(url).bind(url, handler);--->HeaderExchanger.bind(URL url, ExchangeHandler handler)--->new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); (武器同样被层层包装)--->Transporters.bind--->NettyTransporter.bind(URL url, ChannelHandler listener)--->new NettyServer(url, listener);(nettyserver已经生成了,武器也传给它了)--->super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));(武器又被包装了)--->doOpen();。好了,最后的nettyServer已经启动了。
6.nettyServer如何处理客户端的请求呢?
注意看doOpen中的pipeline.addLast("decoder", adapter.getDecoder());pipeline.addLast("encoder", adapter.getEncoder());pipeline.addLast("handler", nettyHandler);这三句。这点要随便找点简单的netty通讯就知道了。server会收到数据,也会发数据,有上行下行过程,在pipeline中被处理。处理包括编码、解码,还有后续处理。
比如收到了客户端的请求了,前面的知识点中介绍了解码的过程,就是从buffer中拿一堆byte[],按着头部16位看数据情况(包括2位magiccode,reqid,还有数据长度;body中的真正的数据),标准的请求过来了,就转成从byte[]转为request(dubbo定义的request协议)过程。
再看nettyHandler,当请求过来了,也转码好了,就轮到nettyHandler来处理了。nettyHandler就是new NettyHandler(getUrl(), this);,其中的this是啥?就是NettyServer。nettyHandler主要处理4种情况:channelConnected/channelDisconnected/messageReceived/writeRequested/exceptionCaught,重点介绍收到请求怎么办?messageReceived方法中是handler.received(channel, e.getMessage());handler是啥?就是NettyServer。说明nettyHandler是NettyServerr的探子,NettyServer当open后,让nettyHandler监控各种情况,再回来向NettyServer汇报,当然NettyServer会处理的。
NettyServer的父类AbstractPeer中有处理--->received(Channel ch, Object msg)--->handler.received(ch, msg);。前面刚说过NettyServer在nettyHandler中是当作handler来处理的。现在它内部处理时,又调用它自己的handler来处理。说是我对我的探子来说我是处理者,处理的时候我又用我内部的处理者处理。内部的处理者是谁呢?就是前面new NettyServer时传入的那个层层包装的武器啊(就是handler.received(ch, msg);,把通道和数据都交给武器来处理了)。
7.层层包装的武器如何处理呢?
下面两句:
handler=new DecodeHandler(new HeaderExchangeHandler(handler)),再被下一句包装
ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))
其中中线程池处理的后一句不多说,里面有一个HeartbeatHandler。收到的消息已经是netty解码过的,不是byte[]了,已经是request消息了(头和body都解码了,body中是放的DecodeableRpcResult),HeartbeatHandler发现isHeartbeatRequest(message),就new Response(req.getId(), req.getVersion();channel.send(res);,直接用通道发回去了,里面的武器都用不上了。如果是标准的调用响应,就从DecodeHandler开始吧。--->decode( ((Response)message).getResult());(netty解码后是消息body中的DecodeableRpcResult,消息体内才是调用结果,要再解码Decode出方法的参数类型,值等信息)--->DecodeableRpcResult.decode()--->handler.received(channel, message);(再交给下一步的handle处理)--->HeaderExchangeHandler--->handleResponse(channel, (Response) message);(方法内可以看到其它一些功能,如果是请求,还有其它的处理方法)--->DefaultFuture.received(channel, response);--->future.doReceived(response);--->lock.lock();response = res;done.signal();lock.unlock();。
8. 数据终于回来了
最高请求的时候,我们分析的是同步请求,那个请求线程如果不超时,还在那里等着呢。上面看到返回值线程锁定后,置好返回值,就发出了信息给等待线程的那个get()方法。这下完整的过程都结束了。
四、结束语
看完基本的代码感悟颇深,看了许多java知识书,设计模式书,感觉都是工具书或者入门书,只有看这样的源码,再深入体会设计开发人员的思想,把很多知识点融合在一起,才有一个质的提高。
前两天看到一点hadoop的源码分析中的各部分之间的同步调用,线程等待也是这样,互相借鉴嘛。hadoop中通讯只用nio的实现,因为都是内部的东东,不需要留下很多口子给别人扩展,简洁有效。在我看来dubbo在技术的道路的走的很远,很高,但原始的功能只是实现一个远程rpc,但包罗万象真的好吗?一些产品,特别是一些内部产品,真的需要做成又大又全的黑盒子给别人用吗?如果用约定去简化很多扩展,或者直接产生多种版本,给别人一个相对简单的白盒子,难度降低后,项目中技术人员可以根据特殊情况完善白盒子,类似于把产品开发的实力,一部分转移到使用产品的项目中,会不会项目更好?
dubbo协议的核心就是实现了headExchange层(头交换层)的Request,Response的定义与encoding,decoding,以及异步转同步,请求与响应的处理以及此层的心跳。它的下层由不同的传输层(如netty)来实现,对不同的传输工具进行了包装。
网上已经看到一些源码分析,总感觉缺少点什么,看完文章还是感觉云里雾里的,可能是自己水平不够吧,所以想从其它角度,先少用代码,重点介绍理念与方案,用自己的话写出来,不一定正确,欢迎指导!!!
许多资料书只是工具书,只有在源码的综合使用过程中才能有更深的体会,才能碰到问题时,有直觉上的方案!
也许已经写过很多很多代码,真正有价值的代码真的多吗?Dubbo源码确实有助于提高整体水平!
一、Dubbo要干什么?
远程跨应用调用。就是这边有接口,那边有实现。那么这边产生一个代理对象(即:Proxy.newProxyInstance)实现接口,对于任何请求,都把请求的东西(主要有方法名,参数类型,参数值)发出去。那边的应用会根据一些信息,找到一个exporter,而它持有一个invoker(即:new AbstractProxyInvoker,这个不是通常生成动态代理的标准invoker,而且它的方法invoke()也可以换成其它名字。),因为它持有真正的接口实现类,所以再根据方法名字反射得到值,再返回给请求方。
二、知识点
1.动态代理
比如jdk动态代理,这个资料很多。简单的使用,就是有一个接口,写一个invoker,持有实现类。再通过Proxy.newProxyInstance返回一个代理对象,它实现了所有的接口,而所有的调用都变成invoke,并传来方法与参数。这时再对持有的实现类反射调用就可以了。在反射调用前后可以做点什么,这点很重要(事务啊,计时啊,推动工作流啊,读缓存啊.....)!!
2.层层包装
这算设计模式了。主要用在Dubbo中的各个handler之间。举个现实栗子,公司门口有人送信来,首先门卫那到,它有一个处理方式,如果是垃圾信就丢掉。重要的交给收发人,同时把送信人告诉收发人。收发人看到是简单问候,直接就让送信人回复了,如果是重要的信,再交给领导,同时把送信人信息告诉领导。领导拿到后,进行处理,结果直接给送信人,这样就处理完了。如果有一天要与歪果人打交道,在门卫收信前设置一个翻译人,他收到,如果是中文就给门卫,如果是外文,先翻译再给门卫,当然翻译接触送信了,所以这时由他把送信人信息给门卫。每个人都有一个handle方法,每个人除了领导都引用下一个处理人,同时把送信人信息交给对方。 有句话说,人的本质是各种社会关系的总和,感觉在面向对象的编程中,对象的本质是各种引用关系的总和。
3.内部类与模板方式
一般抽象类中写公共方法,不同的了类中写不同的方法。如果一个方法中有一部分相同,一部分不同,那这个方法就拆成2个方法,不同的部分成为抽象方法在子类中实现。有时候这个不同,只有在使用时都知道有什么不同,那干脆就只在new这个抽象类时补充上不同的那个抽象方法,有点象匿名类(new AbstractProxyInvoker的时候就是这样。见JdkProxyFactory类。)。有时候这个不同的部分包装成实现通用接口的对象,那就是回调了,比如hibernateTemplate中经常用的。看情况选用吧。
4.从具体到抽象
通常一个大的应用中会用到已有的东东。为了灵活性,会使用不同的已发明的轮子,但轮子尺寸不一,咋办?包装啊。比如说:我要把消息给远方的朋友,我可以打电话,可以发email,可以QQ,可以写信。利用的都是已经有的通讯方式,不过感觉麻烦,我只想说一句话啊。那干脆雇佣几个人,一个会写信,一个会打电话,一个会email,那我只要选择其中一个人(好象是自动发现机制吧),对他们说句话,就OK了,其它事情他们搞定。Dubbo可以用netty,也可以用其它方式作为transport的工具,反正都是已有的轮子,包装起来用。Dubbo会有NettyClient,GrizzlyClient...反正他们都实现通用的方法。
5.netty通讯
netty是基于nio的通讯框架。基本原理不说了,重点体会NettyClient、NettyServer、NettyHandler、NettyChannel、以及和ChannelHandler的关系。NettyClient的两面性特别注意。NettyHandler构建的时候把NettyClient当成ChannelHandler来用,因为它持有ChannelHandler,而它本来是个client。
NettyHandler可以监控任何nio通讯事件,监控到了,它就让引用的ChannelHandler(NettyClient)来处理事情,并把【来信通道】告诉处理的人。而处理的人就象前面介绍的门卫、收发人,领导那样依次处理。如果是要回复的处理(twoway),就直接用【来信通道】把最后的处理结果返回就OK了。
从命名看功能:client一般有close(),connect(),reset()等方法,Channel一般有send()等功能,ChannelHandler一般是利用Channel来做些事情。
6. HeaderExchanger
当把客户端的调用信息,远程发送给服务器时,当然需要netty这样的通讯工具来帮忙。比如我们经常要让远方的朋友帮忙做事时,有时候让邮局送信,有时候让快递送信,有时候让镖局送东西。太麻烦了,我干脆在自己这边成立一个送信办事处,朋友那边成产一个办事处。办事处之间定时通讯一下(心跳),看着通讯有效不。服务点开张的时候,默认选择一个邮局(nettyclient)来用。办事处的通讯也有一些功能象client,也有一些功能象channel。所以就分成HeaderExchangeClient与HeaderExchangeChannel吧。办事处不象邮局那样处理各种各样的东东,我们自己的办事处只处理自己的request与response。主要的包括心跳请求,办事请求与相应的响应,还设计请求的同步与异步,同步时请求线程进入wait。这些格式就自己来设计了。
7.netty的encode/decode
前面讲netty后,缺少了一个重要步骤。netty传输时,读写都是对ChannelBuffer操作,就算是临时仓库吧。writeBytes、readBytes是ChannelBuffer的功能。得到的byte[]需要相互转换request与response。设计好的request转成byte[]是这样的,【头部】一共16位byte。01是叫magic码,2.3是请求标识或者twoway之类的,4-11是请求的ID,12到15是记录后面的数据的长度。response头部也是magic码,2是事件标识,3是响应码,其它不一一道来了。这样,来了请求或者响应,可以组成一堆byte[]放仓库,当仓库里来了一堆byte[],也可以分析出是啥东东。也有分析不出来的,返回DecodeResult.NEED_MORE_INPUT这个枚举对象。当你用netty实现httpserver时,你可以用现成的decode得到一个httprequest对象。
8 重要的对象关系
HeaderExchangeClient拥有一个NettyClient,还拥有一个HeaderExchangeChannel。但HeaderExchangeChannel做的事情还是找NettyClient来做,还记得前面说过NettyClient的两面性,即象client,又象channel。
NettyClient更厉害了,它除了是client外,因为持有chanel(最核心的netty里的chanel),可以直接用chanel发送东西出去。构造的时候因为持有外部传给它的ChannelHandler,本来已经包装过了(new DecodeHandler(new HeaderExchangeHandler(handler))),它还再包装了一下(wrapChannelHandler(url, handler)),所以可以处理nettyhandle中的通道事务。nettyhandle是NettyClient生成的用来守着通道的,告诉它,有情况来找我,因为我持有ChannelHandler,我也可以变身ChannelHandler。
9 重要的处理过程
层层包装的ChannelHandler处理channel事务,包装的同时还传递着channel供其使用。从这两句看的出来:new DecodeHandler(new HeaderExchangeHandler(handler) 与new MultiMessageHandler(new HeartbeatHandler(....)。最原始的Handler是DubboProtocol中的ExchangeHandlerAdapter的匿名子类,在它外面一直在包装。等到了nettyhandle用NettyClient来处理的时候,要一层一层的从外到里处理了。
比如:HeartbeatHandler处理时,发现是心跳请求,直接产生一个心跳回复就OK了,用channel发出去。HeartbeatHandler不会再传递给内部了。如果不是心跳请求,传给HeaderExchangeHandler处理,并把channel给它。如果HeaderExchangeHandler发现是响应,它要把响应结果放好,并唤醒请求时等待的线程,说:返回值来了,醒醒,该干活了。如果发现是真正的请求,HeaderExchangeHandler还要把channel和请求转给DubboProtocol中最原始的Handler来处理。原始的Handler就不传递了,调用reply()来找到exporter,再找到invoker,处理后返回值。
10 java的SPI机制及dubbo扩展,及为啥扩展
前面就提到自动发现ExtensionLoader.getExtensionLoader(...)这样的语句,使用的是SPI机制。SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。这么说吧,一般都是对接口编程,具体的实现类不同可以实现不同的功能。如果实现类让别人扩展,那只要告诉我实现此接口的类在哪?找到就可以实例化。我司的一个应用中,把实现类放在数据库中也达到此目标。SPI原理就是就放在META-INF\services里面就可以找到了,不足之处是把所有的实现都给你了。你也许只想用某个特殊的实现类呢?
dubbo先实现了一个此接口的Adaptive类,实现的的方法是用代码写一个java文件(createAdaptiveExtensionClassCode()),再用代码编译(compiler.compile(code, classLoader)),再加载进来。这个Adpative类实现了接口的所有标注了adaptive的方法。当调用这个adaptive类的方法时,这个daptive类从方法的参数中提取一个重要的值,根据这个值再调用真正的实现类。这简直是灵活性++啊,当然难度^2啊。
为啥这么做呢?还是先举个栗子,有人送东西来你家,你家好几个人,不知道谁来收。送东西的人说收货人信息封在里面,不能拆。怎么办,要么都过来拿,不是自己的东西的人就白走一趟。要么找个代理人,他可以收任何人的东西,收到后拆开,根据里面的真正的收货人,叫某某来拿。这个代理人就是daptive类。你可以直接定义这个代理人,情况比较多时,恩恩,这里存在某种看似重复的东东,那可以动态生成这个代理人嘛。为啥送货人不能直接看到关键的收货人信息呢?这么做简直就是.....是炫耀技术。
11 线程池
前面提到了心跳,心跳是一种定时执行的任务,在HeaderExchangeClient中就有ScheduledThreadPool来不断发送心跳请求。java中有四种线程池,这个找资料都都搞明白。包括什么核心线程数,最大线程数,无界队列什么的...
不过这里有几个值得学习的地方:首先,HeartBeatTask中有一个ChannelProvider接口定义,还持有实现这个接口的对象,构造Task时传入的。当要new HeartBeatTask放入线程池时,new的同时,实现这个类中接口的匿名类。高手们写代码就是高深。但想想为啥这么写呢?定时任务主要的功能是把心跳消息从各个通道发出去。提供通道他并不关心,最好由外部来实现,作为匿名类传进来就好了,省得绑定太密切,影响独立性。其实,ScheduledFuture作为启动线程池返回的对象,可以比如好的结束定时任务线程池heatbeatTimer.cancel(true);以前没怎么用过。
12 同步锁 ReentrantLock
synchronized是比较常用的线程同步用法,要求高点就用concurrent里的东东。ConcurrentHashMap是多线程中用的比较多的容器,线程同步并且效率高。AtomicLong用来对request计数,使用cpu的什么cas保证线程安全。dubbo在请求后,就马上去取值,没有值就wait(),这里面有一个超时时间,就要用到ReentrantLock.newCondition()的这样的条件了。一旦返回值来了,就done.signal();通知等待的线程来读数据了。普通的wait(),notify()功能太少了吧。顺便提一下ChannelHandlerDispatcher中的CopyOnWriteArraySet吧,读写分享的思想,读不加锁,而且读应该远超过写的情况。如果写的开销比较大,不分开的话,要么读也加锁保证一致性,如果不加,读的会很混乱。写肯定要加锁,写的时候写复制出的东东,利用空间换时间。
三、把代码把知识点串起来
下面开始把客户端产生代理,到得到返回值的整个过程,用代码串起来,当然上面的知识点也都串了起来,开始撸~
1. 配置到spring中,利用spring解析xml配置时初始化整个过程。
spring加载配置后执行afterPropertiesSet,就从
com.alibaba.dubbo.config.spring.ReferenceBean的afterPropertiesSet()开始跟踪。
-->getObject()--->get();--->init();-->createProxy(map)--->invoker = refprotocol.refer(interfaceClass, urls.get(0));--->return (T) proxyFactory.getProxy(invoker);
最后一个就是得到客户端的服务代理,重点是refprotocol.refer(interfaceClass, urls.get(0))方法得到invoker。下面直接到com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol看 refer(Class<T> serviceType, URL url) 方法。里面有个new DubboInvoker<T>(serviceType, url, getClients(url), invokers);方法。
2.产生invoker前做了很多事情
注意:先看getClients(url)参数,--->initClient(url);---->Exchangers.connect(url ,requestHandler);(requestHandler是重要的处理武器,后面会被层层包装的,前面知识点有提到)---->getExchanger(url).connect(url, handler);(这里要找一个exchanger,再用connect方法)---(自动发现机制?)->HeaderExchanger.connect(URL url, ExchangeHandler handler)---->new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));(看到了吧,出现了HeaderExchangeClient,它还持有一个传输者,还把那个处理武器包装后交给了它,后面继续找Transporters)--->getTransporter().connect(url, handler);--(自动发现机制?)->NettyTransporter.connect(URL url, ChannelHandler listener)---->new NettyClient(url, listener);---->NettyClient new的时候,在父类AbstractClient构造时--->doOpen();connect();
这下都清楚了吧,在产生DubboInvoker之前,生成了HeaderExchangeClient,它持有NettyClient,同时把原始武器requestHandler包装后最好交给NettyClient,NettyClient打开,并开始连接server了。这部分只是介绍了DubboInvoker生成中的其中一个参数。下面就可以生成DubboInvoker了。
3.invoker做什么事情?
DubboInvoker要用来生成代理对象的,所以它最重要的方法就是invoke(Invocation inv) ,这个方法在父类AbstractInvoker里。子类实现了个性化的doInvoke(final Invocation invocation)部分(模板模式)。参数为啥变成final了?哈哈,这个自己再找答案,这里不讲了。
doInvoke(final Invocation invocation) 中,对于同步请求,就执行return (Result) currentClient.request(inv, timeout).get();这句。
其中currentClient就是前面getClients(url)中得到的,姑且认为就是HeaderExchangeClient吧,它用来把inv(包括调用接口、方法、参数、值等信息)发出去,还设置了超时间,半天不返回就不要了。最后一个get(),就是从com.alibaba.dubbo.remoting.exchange.support.DefaultFuture中取返回值,这时候就使用了lock,取不到值就等待,等被唤醒了就isDone()--->returnFromResponse();--->return res.getResult();。终于拿到返回值了。
4.如何把请求发出去?
currentClient.request(inv, timeout)是上面过程中的最重要的一个方法了。
request(Object request, int timeout) ---> channel.request(request, timeout);--->channel.send(req);
详细说一下上面的:currentClient的请求,转交给channel(HeaderExchangeChannel)来办(channel从名字上就知道是干这事情的),channel对request处理了一下先,req.setData(request);就是new了一个新的req,把传过来的request当成了req的数据部分了,就是加了一个【头,又叫header】。新的请求对象已经组装完成,HeaderExchangeChannel接到任务后也要用它的channel发出去。一环套一环啊,自己的事情都不自己做,都找别人做,还好不开工资的。HeaderExchangeChannel拥有的channel是啥?前面知识点有介绍过就是nettyClient啊,这个具有两面性的东东。看来HeaderExchangeClient不仅拥有它,还把它给了HeaderExchangeChannel来使唤。
nettyClient自己也有nettyChannel,为啥HeaderExchangeChannel不直接引用呢?我们知道netty是一个整体。nettyClient统一收个口子,如果不用netty,只换一下client就行了。符合耦合度低,内聚度高的原则。
5.现在nettyCient如何把请求发出去?
nettyClient --->父类AbstractPeer的send(Object message)--->AbstractClient中的send(Object message, boolean sent)--->Channel channel = getChannel();再channel.send(message, sent); 其中的getChannel();中返回的是NettyChannel.getOrAddChannel(c, getUrl(), this);说明NettyChannel持有netty最核心的channel(org.jboss.netty.channel.Channel ),NettyChannel.send时用这句:ChannelFuture future = channel.write(message);--->write是这个核心channel的发信息的方法。
上面客户端的过程介绍完了,下面是服务器的介绍了。nettyClient发出去的,当然nettyServer要收。nettyClient当然也要收nettyServer返回的结果了。
6.现在nettyServer如何启动
在nettyServer收请求时,首先应该是服务器那边启动了,nettyServer监听各种请求。所以看看服务器启动过程。同样中服务端的spring启动开始。com.alibaba.dubbo.config.spring.ServiceBean.afterPropertiesSet()开始跟踪吧。--->export()--->doExport()--->doExportUrls();--->doExportUrlsFor1Protocol(protocolConfig, registryURLs);--->
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));--->protocol.export(invoker);--->exporters.add(exporter);
其中的proxyFactory.getInvoker就是new AbstractProxyInvoker<T>(proxy, type, url) ,同时实现里面的抽象方法doInvoke。当收到客户端的调用Invocation inv后,可以知道调用的方法名、参数类型,参数值,同时它持有proxy这个接口实现类。那剩下的就是简单的反射得到最后的结果了。invoker有了,下面是protocol.export(invoker);把invoker用协议暴露出来。那就看看com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol中的export方法。
export中主要做了两件事。DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);与openServer(url);。前面是exporter持有invoker并放入一个exporterMap中存下来。后面继续跟踪---->createServer(url)--->server = Exchangers.bind(url, requestHandler);
还记得client时,用的是Exchangers.connect生成HeaderExchangeClient吗?是了,这里用Exchangers.bind生成服务端,记着这里也传入了requestHandler这个武器,用来处理request的。继续跟踪--->getExchanger(url).bind(url, handler);--->HeaderExchanger.bind(URL url, ExchangeHandler handler)--->new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); (武器同样被层层包装)--->Transporters.bind--->NettyTransporter.bind(URL url, ChannelHandler listener)--->new NettyServer(url, listener);(nettyserver已经生成了,武器也传给它了)--->super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));(武器又被包装了)--->doOpen();。好了,最后的nettyServer已经启动了。
6.nettyServer如何处理客户端的请求呢?
注意看doOpen中的pipeline.addLast("decoder", adapter.getDecoder());pipeline.addLast("encoder", adapter.getEncoder());pipeline.addLast("handler", nettyHandler);这三句。这点要随便找点简单的netty通讯就知道了。server会收到数据,也会发数据,有上行下行过程,在pipeline中被处理。处理包括编码、解码,还有后续处理。
比如收到了客户端的请求了,前面的知识点中介绍了解码的过程,就是从buffer中拿一堆byte[],按着头部16位看数据情况(包括2位magiccode,reqid,还有数据长度;body中的真正的数据),标准的请求过来了,就转成从byte[]转为request(dubbo定义的request协议)过程。
再看nettyHandler,当请求过来了,也转码好了,就轮到nettyHandler来处理了。nettyHandler就是new NettyHandler(getUrl(), this);,其中的this是啥?就是NettyServer。nettyHandler主要处理4种情况:channelConnected/channelDisconnected/messageReceived/writeRequested/exceptionCaught,重点介绍收到请求怎么办?messageReceived方法中是handler.received(channel, e.getMessage());handler是啥?就是NettyServer。说明nettyHandler是NettyServerr的探子,NettyServer当open后,让nettyHandler监控各种情况,再回来向NettyServer汇报,当然NettyServer会处理的。
NettyServer的父类AbstractPeer中有处理--->received(Channel ch, Object msg)--->handler.received(ch, msg);。前面刚说过NettyServer在nettyHandler中是当作handler来处理的。现在它内部处理时,又调用它自己的handler来处理。说是我对我的探子来说我是处理者,处理的时候我又用我内部的处理者处理。内部的处理者是谁呢?就是前面new NettyServer时传入的那个层层包装的武器啊(就是handler.received(ch, msg);,把通道和数据都交给武器来处理了)。
7.层层包装的武器如何处理呢?
下面两句:
handler=new DecodeHandler(new HeaderExchangeHandler(handler)),再被下一句包装
ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))
其中中线程池处理的后一句不多说,里面有一个HeartbeatHandler。收到的消息已经是netty解码过的,不是byte[]了,已经是request消息了(头和body都解码了,body中是放的DecodeableRpcResult),HeartbeatHandler发现isHeartbeatRequest(message),就new Response(req.getId(), req.getVersion();channel.send(res);,直接用通道发回去了,里面的武器都用不上了。如果是标准的调用响应,就从DecodeHandler开始吧。--->decode( ((Response)message).getResult());(netty解码后是消息body中的DecodeableRpcResult,消息体内才是调用结果,要再解码Decode出方法的参数类型,值等信息)--->DecodeableRpcResult.decode()--->handler.received(channel, message);(再交给下一步的handle处理)--->HeaderExchangeHandler--->handleResponse(channel, (Response) message);(方法内可以看到其它一些功能,如果是请求,还有其它的处理方法)--->DefaultFuture.received(channel, response);--->future.doReceived(response);--->lock.lock();response = res;done.signal();lock.unlock();。
8. 数据终于回来了
最高请求的时候,我们分析的是同步请求,那个请求线程如果不超时,还在那里等着呢。上面看到返回值线程锁定后,置好返回值,就发出了信息给等待线程的那个get()方法。这下完整的过程都结束了。
四、结束语
看完基本的代码感悟颇深,看了许多java知识书,设计模式书,感觉都是工具书或者入门书,只有看这样的源码,再深入体会设计开发人员的思想,把很多知识点融合在一起,才有一个质的提高。
前两天看到一点hadoop的源码分析中的各部分之间的同步调用,线程等待也是这样,互相借鉴嘛。hadoop中通讯只用nio的实现,因为都是内部的东东,不需要留下很多口子给别人扩展,简洁有效。在我看来dubbo在技术的道路的走的很远,很高,但原始的功能只是实现一个远程rpc,但包罗万象真的好吗?一些产品,特别是一些内部产品,真的需要做成又大又全的黑盒子给别人用吗?如果用约定去简化很多扩展,或者直接产生多种版本,给别人一个相对简单的白盒子,难度降低后,项目中技术人员可以根据特殊情况完善白盒子,类似于把产品开发的实力,一部分转移到使用产品的项目中,会不会项目更好?
dubbo协议的核心就是实现了headExchange层(头交换层)的Request,Response的定义与encoding,decoding,以及异步转同步,请求与响应的处理以及此层的心跳。它的下层由不同的传输层(如netty)来实现,对不同的传输工具进行了包装。
相关推荐
《Dubbo源码深度解析与实战应用》 Dubbo,作为阿里巴巴开源的一款高性能、轻量级的Java服务治理框架,其源码的学习对于提升开发者在分布式系统设计与实现上的理解至关重要。本文将深入剖析Dubbo的核心组件、工作...
【标题】"Dubbo学习视频教程"所涉及的知识点主要集中在分布式服务框架Dubbo的使用、配置、原理以及相关的开发工具上。Dubbo是阿里巴巴开源的一款高性能、轻量级的Java RPC框架,它提供了丰富的服务治理功能,如服务...
通过这个 "dubbo-master" 压缩包,开发者可以深入了解 Dubbox 的工作原理,学习如何使用 ZooKeeper 进行服务注册与发现,并实践 Java Web 应用的构建和部署流程。这对于提升在分布式系统和微服务架构下的开发能力...
在学习这两个框架的过程中,你需要掌握以下几个关键知识点: 1. **Activiti的流程设计**:理解BPMN 2.0标准,学习如何使用Activiti Designer创建和编辑流程图,以及如何将这些流程部署到Activiti Engine。 2. **...
4. **源码阅读与分析**:通过阅读Dubbo的源码,学习过滤器的内部工作原理,以及如何自定义过滤器以满足特定需求。 5. **开发实践与优化建议**:根据提供的文档和开发建议,理解如何在实际项目中合理运用过滤器,...
这个项目涉及的知识点广泛,包括但不限于SpringBoot的MVC原理、Zookeeper的集群管理、Dubbo的RPC机制、分布式锁的实现(可能用到Zookeeper)、数据库优化(如分库分表)、并发控制策略(如乐观锁、悲观锁)等。...
### Java高级架构进阶学习知识点概述 #### 一、思维导图 - **知识点概览**:思维导图是理解Java高级架构的关键工具之一。它能够帮助开发者快速掌握架构设计的核心概念和技术要点。 - **核心内容**: - **基础知识...
标题“离开完美世界去阿里总结下面试过程”揭示了作者从一家知名的游戏公司——完美世界离职,并经历了阿里巴巴的面试流程,从中我们可以提炼出一些IT行业的关键知识点。 首先,从“完美世界”我们可以联想到游戏...
这份"Java demo 算法笔记"集合了Java开发中的多种关键知识点,包括但不限于基础语法、框架源码解析、算法实现以及并发处理等内容,对于学习和提升Java编程技能具有极大的帮助。 首先,我们来探讨Java的基础部分。...
理解这个文件涉及的知识点包括:GIF文件格式、图像编码原理、帧管理和时间同步。 再者,`LZWEncoder.java`与LZW(Lempel-Ziv-Welch)压缩算法有关。LZW是一种无损数据压缩算法,广泛应用于GIF图像格式中。在Java中...
2.全程案例实战驱动讲解和动手演练,每个知识点都会通过实际的代码样例来演示其原理和特性,以模拟真实的案例来驱动讲解各种技术点,帮助同学们在业务背景中理解和掌握复杂的技术。 3.测试驱动开发,整个课程全部...
9. **JDK源码分析**:对ArrayList、HashMap、LinkedList等核心类的源码有深入的理解,能够分析其工作原理。 10. **Spring框架**:理解依赖注入(DI)和面向切面编程(AOP),掌握Spring的核心组件,如Bean管理、...
《MinicRPC:仿制“生日管家”系统详解》 在IT行业中,开发个人项目是提升技能、理解和应用新技术的有效途径。...通过深入学习和实践这些知识点,开发者不仅能提升技术技能,还能积累宝贵的项目经验。