Dubbo接口测试调试工具(三) -- 项目设计
聊的不止技术。跟着小帅写代码,还原和技术大牛一对一真实对话,剖析真实项目筑成的一砖一瓦,了解最新最及时的资讯信息,还可以学到日常撩妹小技巧哦,让我们开始探索主人公小帅的职场生涯吧!
(PS:本系列文章以幽默风趣风格为主,较真侠和学习怪请绕道~)
江华:“哟,小帅,又在写bug啊?”
小帅:“滚......”
老胡:“小帅,让你写的工具进度怎样啦?”
小帅:“正是愁着这个呢!老胡,我感觉无从下手呀,你说这个项目应该怎样开展的呢?”
老胡:“原来你几天茶饭不思,连小檬都不撩了就是为了这个啊!”
小帅:“对啊,老胡,快教教我。”
老胡:“看在你这么努力(考顺)的样子,我就勉为其难指点你一下啦。”
江华:“......老胡你吃着小帅买的士力架说这话良心不痛吗?”
老胡:“咳咳咳,啊哈哈,我们看下怎样开始这个项目吧!”
老胡:“小帅,你可以先把学校里那套自上往下、自下往上的软件设计理论先放一边,嗯对,因为我也记不住。其实这个项目没那么复杂。”
老胡:“你以前不是学过画画么,我来问你,如果让你画一只猫,你会这样做?”
小帅:“en...首先确定这只猫长什么样,然后确定它在纸张摆放比例,然后做辅助线把猫分成头、身、腿、尾巴等几部分,然后根据五官比例作辅助线把头部又分成眼、耳、口、鼻、嘴等几个小部分,然后就可以开始画啦,等等,你想说的是,这个跟写代码是一样的?”
老胡:“对的,思路是一样的,下面我们一起分解说明一下。”
1、首先确定项目的总体功能。我们要的功能很简单,输入 --》转发 --》展示。
图1 总体设计
2、接着,我们拆分输入部分。
老胡:“说这个之前,小帅,我来问你,假如让你来设计一个RPC框架,你会怎样设计呢?”
小帅:“en...我想想哈(不好意思表情)。”
江华:“老胡,你太看得起他了。”
老胡:“或者这样问,你觉得都应该包含哪些东西?结合你平时使用Dubbo的场景想一想。”
小帅:“en...之前使用Dubbo的场景啊,哦对了,先定义一个接口,然后在生产者写实现,再然后注册到Zookeeper,然后消费者也注册到Zookeeper,然后就跟使用本地接口方法那样了,那是不是一个RPC框架就应该包含接口定义、生产者、消费者、注册中心?”
老胡:“你这样回答也不能说错。RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。既然是远程调用,那么必定存在一个服务端,也就是你提到的生产者。知道有服务端还不够,我们得知道服务端的IP和端口,因为计算机网络都是通过Socket(套接字)来进行通讯的。那么我们是怎样知道服务端的IP和端口的呢?”
小帅:“我知道,Dubbo是通过Zookeeper暴露的服务的。它好像会把IP、端口、接口、方法这些拼接成Url,然后写到Zookeeper的/dubbo/xxx/providers 节点上。”
老胡:“纠正一下,虽然Dubbo推荐使用Zookeeper作为注册中心,但注册中心并不一定只能用Zookeeper哦。”
江华:“Dubbo目前支持4种注册中心,(multicast,zookeeper,redis,simple)”
老胡:“对的,当然,RPC框架不一定需要使用注册中心,但有了它可以做很多东西。好了,服务端的IP和端口都知道了,我们还需要一种协议,你可以理解为一种服务端和客户端都能听到的语言,也就是小帅你所说的接口。而接口会包含接口名称、方法名称还有参数这些东西。”
再回想一下Dubbo Consumer的使用方法。
1、配置zookeeper地址。
2、引用API的jar包
3、输入接口、方法、参数等。
老胡:“输入部分跟Dubbo保持一致,这样一来,我们的工具就可以不改变用户习惯的前提下充当一个泛化Dubbo客户端了。”
图2 分解输入
3、接着,我们拆分转发部分。
1、获取Provider的IP和Port。
老胡:“前面已经提到,我们可以通过注册中心拿到服务器的IP和地址,也就是所谓的服务发现。Provider会把自身的一系列参数拼接成url,然后存放到zookeeper的provider节点上。所以我们只需要从Zookeeper获取Provider的所有节点,稍作解析就能得到Provider列表,从而得到IP和Port。”
2、连接Provider。
老胡:“获取到服务器地址以后,下一步当然是连接Provider了。还记得Consumer和Provider默认用什么通信框架的吗?”
小帅:(挠头表情)
江华:“默认是Netty吧,Provider暴露服务的时候会启动一个Netty Server。”
老胡:“是的,Provider在初始化interface的时候,除了会向zookeeper注册,还会通过export方法在本地启动Netty服务器,监听暴露的端口,等待客人送上门。咳咳,等待连接建立。而Consumer则会启动一个Netty客户端和Provider建立单一长连接来收发数据。”
小帅:“所以我们可以写一个Netty Client,然后连接上Provider的Netty Server?”
老胡:“没错。”
小帅:“哦哦,这个我懂,我可以写一个ClientBootStrap,嘿嘿。”
江华:“这就那么开心,小萌新真容易满足。”
小帅:“......”
图2 连接服务器
3、收发数据
老胡:“连接上Provider后,下一步就可以收发数据了。小帅,书刚看完,这块你还记得吧?”
江华:“怎么可能记得,他估计都要找当当退货去了。”
老胡:“......”
小帅:“......”
小帅:“我记得Netty连接建立后会返回一个Channel,这就是数据传输的管道。调用write方法就可以写入数据,推送到服务器了。”
小帅:“等等,老胡,我记得我之前看Demo的时候,除了自己ChannelHandler,好像还需要向Pipeline添加一个encode和decode的ChannelHandler。”
老胡:“是的,数据包在网络传输是二进制字节流,只有1和0我们工具和Provider都识别不了,所以这里还涉及到序列化和反序列化过程。这块先不急,后面我会教你,通过薅Dubbo的羊毛来实现。”
小帅:“哦哦,这样啊。那这个数据包就是我们前面输入部分提到接口、方法、参数这些吗?”
老胡:“是的,我们把这些信息封装成规定格式的数据包,然后编码,经过网络传输,然后服务端解码,然后分离出接口类、方法、参数,服务端就可以通过反射执行对应的方法了。”
小帅:“哇塞,原来是这样的啊!”
江华:“你好像好惊讶的样子...”
小帅:“...你就喜欢装什么都懂。”
4、响应。
老胡:“请求包发送出去了,我们得拿到响应,不然后面的展示模块就没发玩了。怎样知道响应报文对应哪个发送报文呢?”
小帅:“江华,你懂你说啊。”
江华:“切,我当然知道,Dubbo在Request包中封装了一个mId的流水号,通过它就可以跟踪发送的包了。”
老胡:“是的,流水号在分布式系统幂等去重、顺序重整、异步调用跟踪等起着重要的作用。在封装数据包的时候,我们可以把流水号记录下来。然后可以阻塞等待这个流水号数据包的响应。小帅,还记得Netty的事件机制吧?”
小帅:“记得记得。”
老胡:“刚才提到可以往Pipeline里添加一个自定义ChannelHandler,用来监听数据接收事件。当服务端执行完方法调用,把数据包返还回来。编码、网络传输、到客户端解码,我们就可以拿到这个流水号了,然后修改这个流水号的等待状态,结束阻塞,这样就可以把数据返回前端页面展示了。”
最后,分解展示部分
老胡:“展示部分比较简单,直接把RpcResult对象转换一下就可以输出到页面了。”
小帅:“哦哦,感谢老胡,我知道怎样做啦。我这就去写代码嘿嘿,告辞!”
老胡:“这孩子,年轻真好。”
江华:“呵呵,老胡,你等着,等下他肯定会回来。”