工欲善其事必先利其器,写一个RPC框架,最最重要的就是抓住它的灵魂,网络模块无疑就是整个RPC的Soul,如何完成一个高质量的网络通讯的底层框架,我现在还没有掌握,哈哈,没有逗你玩的意思,不管是Netty还是Mina,这些通讯框架还是离不开对协议的支持,TCP协议,一些参数配置,里面的水还是很深的,每个TCP的配置参数我们还是需要做一些了解,这样才能做到优化网络传输模型,提高所谓的QPS
现在N多RPC框架应该选用的都是Netty,基于多种原因吧:
1)它很容易上手,它封装了网络传输底层的实现,但也有Spring的开闭原则,可以根据用户的参数配置去定制属于自己的网络传输模型
2)它优化了线程模型,且基于事件驱动,基于NIO,提高IO效率
3)内存优化,直接内存的使用/池化的技术
4)第四点,我也是认为很重要的一点,也是我们写这个RPC框架的核心点,就是有很多参考,基于Netty的成熟的生产级别的代码可以参考(本RPC框架的网络模块netty应用是融合Jupiter和RocketMQ的),说白了,有点抄袭,但也是做了部分的修改
laopopo-rpc的网络框架的模型就是参考的RocketMQ,(希望大家还是写过Netty的HelloWorld的)
说到网络传输,我们不得不提,网络传输对象的问题,我们设想一个场景,假如A系统发送某个信息给B系统,为了防止粘包和拆包的问题,我们需要为这个信息量身定做一个编码器和译码器,B系统给C系统发送一个信息,我们还需要些一个编码器和译码器,这样就会大大加大我们的工作量,所以我们需要统一我们的传输对象,写一个通用的解码器和译码器,这样做的好处是:
1)统一唯一的网络传输对象方便Netty对网络对象的编码和解码
2)统一的编码和解码也解决了TCP 粘包和拆包的问题
3)方便我们编码,不需要花很多心思去处理数据的网络传输,不需要为了每一个请求都要写一个处理器handler和(解码器)decoder和(编码器)encoder,统一处理
不记得哪一天,我无意中看了RocketMQ的源码,看到了这些大神设计的思路,所以我们借而用之:
我们网络传输对象叫做RemotingTransporter,哈哈,远程传输对象
package org.laopopo.remoting.model; import java.util.concurrent.atomic.AtomicLong; import org.laopopo.common.protocal.LaopopoProtocol; import org.laopopo.common.transport.body.CommonCustomBody; /** * * @author BazingaLyn * @description 网络传输的唯一对象 * @time 2016年8月10日 * @modifytime */ public class RemotingTransporter extends ByteHolder { private static final AtomicLong requestId = new AtomicLong(0l); /** * 请求的类型 * 例如该请求是用来订阅服务的,该请求是用来发布服务的等等的 * 假设 code == 1 代表是消费者订阅服务,则接收方注册中心接到该对象的时候,就会先获取该code,判断如果该code==1 则走订阅服务的处理分支代码 * 假设 code == 2 代表是提供者发布服务,则接收发注册中心接收该对象的时候,也会先获取该code,判断如果该code==2则走发布服务的处理分支代码 */ private byte code; /** * 请求的主体信息 {@link CommonCustomBody}是一个接口 * 假如code==1 则CommonCustomBody中则是一些订阅服务的具体信息 * 假如code==2 则CommonCustomBody中则是一些发布服务的具体信息 */ private transient CommonCustomBody customHeader; /** * 请求的时间戳 */ private transient long timestamp; /** * 请求的id */ private long opaque = requestId.getAndIncrement(); /** * 定义该传输对象是请求还是响应信息 */ private byte transporterType; protected RemotingTransporter() { } /** * 创建一个请求传输对象 * @param code 请求的类型 * @param commonCustomHeader 请求的正文 * @return */ public static RemotingTransporter createRequestTransporter(byte code,CommonCustomBody commonCustomHeader){ RemotingTransporter remotingTransporter = new RemotingTransporter(); remotingTransporter.setCode(code); remotingTransporter.customHeader = commonCustomHeader; remotingTransporter.transporterType = LaopopoProtocol.REQUEST_REMOTING; return remotingTransporter; } /** * 创建一个响应对象 * @param code 响应对象的类型 * @param commonCustomHeader 响应对象的正文 * @param opaque 此响应对象对应的请求对象的id * @return */ public static RemotingTransporter createResponseTransporter(byte code,CommonCustomBody commonCustomHeader,long opaque){ RemotingTransporter remotingTransporter = new RemotingTransporter(); remotingTransporter.setCode(code); remotingTransporter.customHeader = commonCustomHeader; remotingTransporter.setOpaque(opaque); remotingTransporter.transporterType = LaopopoProtocol.RESPONSE_REMOTING; return remotingTransporter; } //省略Getter和Setter toString }
ByteHolder.java
package org.laopopo.remoting.model; /** * * @author BazingaLyn * @description * @time 2016年8月9日 * @modifytime */ public class ByteHolder { private transient byte[] bytes; public byte[] bytes() { return bytes; } public void bytes(byte[] bytes) { this.bytes = bytes; } public int size() { return bytes == null ? 0 : bytes.length; } }
我怕大家还是不理解上面这个类的用途,还是再解释一遍吧,其实很简单,我们学过netty的HelloWorld都知道,网络传输中,都会有一个编码器和译码器,编码器和译码器是成对的,通信的双方按照规定进行编码和译码,而不是各自编各自的,各自译各自的,那就相当于鸡同鸭讲,不知道说的是什么了
除开RPC这个大背景,假设有A 系统发送一个一些学生的信息List<Student>的信息,或者一个Teacher的信息给B系统,用我们上文规定的那个RemotingTransporter去发送,A系统的程序猿说,当RemotingTransporter中的code == 1的时候,表示我发送的学生的信息,等于二的时候就是一个Teacher的信息,这些信息数据放在ByteHolder的属性bytes中,你获取到之后,反序列化一下,就可以了,B按照A程序员的规定去做,
if(code== 1){序列化List<Student>}else{序列化Teacher} 果然没有问题
A程序员也很轻松的构建了网络传输的对象
package org.laopopo.example.netty.example_1; import java.util.ArrayList; import java.util.List; import org.laopopo.common.exception.remoting.RemotingCommmonCustomException; import org.laopopo.common.transport.body.CommonCustomBody; import org.laopopo.remoting.model.RemotingTransporter; public class NettyTransporterTest1 { public static final byte STUDENTS = 1; public static final byte TEACHER = 2; public static void main(String[] args) { StudentInfos infos = new StudentInfos(); //学生信息 RemotingTransporter studentsRemotingTransporter = RemotingTransporter.createRequestTransporter(STUDENTS, infos); //学生信息 TeacherInfo info = new TeacherInfo(); RemotingTransporter teacherRemotingTransporter = RemotingTransporter.createRequestTransporter(TEACHER, info); } private static class StudentInfos implements CommonCustomBody { List<String> students = new ArrayList<String>(); @Override public void checkFields() throws RemotingCommmonCustomException { } public List<String> getStudents() { return students; } public void setStudents(List<String> students) { this.students = students; } } private static class TeacherInfo implements CommonCustomBody { String teacher = ""; @Override public void checkFields() throws RemotingCommmonCustomException { } public String getTeacher() { return teacher; } public void setTeacher(String teacher) { this.teacher = teacher; } } }
然后可以通过Netty的一些API就可以将这个网络传输模型发送给B系统,B系统也可以很方便的去反序列化成有用的信息,A系统向传输 Student,Teacher,User,Book,XXXinfo,只需要将其实现CommonCustomBody接口,而因为java可以实现多个接口,所以这样并不会影响你java类的使用,所以也很简单,好了,我们现在已经定义好了网络传输的模型了,接下来我们看看RPC中对RemotingTransporter的编码器和译码器的编写吧