在看此篇内容时需要浏览下面内容
从零开始学netty——如何面对粘包和拆包
从零开始学netty——自定义协议
rpc大家大概都听说过,远程过程调用。简单来说,就是我的一个操作是远程操作的给的结果,举个例子,考试作弊,你把考题发出去了,你同学帮你做好把答案传输给你,然后你就把答案写上,那么在判卷老师眼里,你回答的还不错,但是,其实你是做了远程过程调用的。
rpc的好处也在上面的例子中体现了,当自身能力不行的时候,可以依靠强大的远程力量来做到结果。如果自己有能力回答卷子,那就没必要走rpc了。换句话说就是
自身执行的消耗 > 别人执行的消耗+传输的消耗
rpc的要点
- 消息传给远程
- 看起来和本地调用差不多
- 服务注册
这里就是建立连接的过程,代码都比较套路,这里不列举。最后会贴出代码地址的,这里先通思路。序列化没有选择pb,而是选择了protostuff,这里得解释一下原因。
既然是方法调用,一个方法的唯一标志是类名,方法名,参数类型。你还得把方法参数也传递走。
private String id;
private String className;
private String methodName;
private Object[] args;
private Class<?>[] parameterTypes;
这里还有传输一个id,是为了做标志,例如我发了题目出去,最希望的就是回到我的是第4题答案是什么,而不是xxxx问题的答案是什么。id就是唯一表示一次问题的。
大家也发现了里面有Object类型和Class类型,这些类型是pb里没有的,所以此时pb就不适合作为序列化的选择了。
收到的就比较简单了,就是唯一的id以及结果
private String id;
private Object result;
netty的返回的结果是在handler里,而不是我们的业务线程,如何传递就成为了一个问题。上面的唯一的id就是解决的关键点,我选择了SynchronousQueue来作为传递的媒介,如果不了解这个类的可以先查看一下,他主要就是作为传递媒介的,有点类似阻塞队列。
private static ConcurrentHashMap<String, SynchronousQueue> mapInfo = new ConcurrentHashMap<>();
使用一个map来保存id,和传递媒介,业务线程只要拿着SynchronousQueue就好,等消息收到,就把结果放入SynchronousQueue中,业务线程就可以拿到结果了,与此同时,要把id从map里移除。
想做到方法调用,还扩充了部分功能,这个是装饰者或者代理模式的效果。因为这里只做一层包装,所以选择代理模式,如果是不断的扩充功能的情况,装饰者会更好一些。因为我们是java编写,动态代理就是一个不错的选择。
public static <T> T getProxy(final Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
request.setMethodName(method.getName());
request.setClassName(clazz.getName());
Class<?>[] parameterTypes = method.getParameterTypes();
request.setArgs(args);
String id = UUID.randomUUID().toString();
request.setId(id);
SynchronousQueue queue = new SynchronousQueue();
ResultInfo.putSunchronousQuee(id, queue);
Client.write(request);
return queue.take();
}
});
}
这里使用了比较简单的方法,就是手动把服务加入。
public static void put(Object value) {
Class<?>[] interfaces = value.getClass().getInterfaces();
for(Class<?> interfaceTmp:interfaces){
services.put(interfaceTmp.getName(), value);
}
}
这里选择把接口作为key,对象作为value。如果结合spring就可以更简单一些,通过注解来做,不用自己手动写了。
这里实现的rpc的基础功能,就是远程调用。技术点就在动态代理和消息通讯上。动态代理的目的是为了让rpc在调用的时候更简单,通讯部分才是rpc的主要点,通过反射等方式,让远程的机器进行运算,并且返回结果。所有的代码如下:https://github.com/xpbob/lightrpc
原创首发于慕课网