什么是RPC? RPC(Remote Procedure Call)即远程过程调用,简单的理解是一个节点请求另一个节点提供的服务,本地过程调用通常是指直接的使用当前程序下的一个方法,而RPC指的是调用远程的不在本机的程序的方法,使用这些方法就好像是在使用本机方法一样,如通常在网络通信时我们有调用远程服务器的方法的需求。
比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议
RPC的优点:
- 提升系统可扩展性,随着业务的增大,可以在新增的服务器上调用原来的接口
- 提升系统可维护性和持续交付能力
- 实现系统高可用
RPC的缺点:
- RPC框架调用成功率受限于网络状况
- 复杂,开发难度大
其中的代码是根据我们前面所设计的MyMessage着手,在这里只做简单的介绍。
MyMessage是所有消息类型的父类
AbstractResponseMessage是所有响应消息的父类,同样继承了MyMessage
1.首先是Rpc调用的请求消息体,就是我的客户端需要远程调用服务器的方法,所以请求消息体里面需要有对应方法的类名、方法名、参数等……,且在服务器里面要根据这些请求消息里的信息用反射进行调用。(这两个消息体在客户端和服务器都需要有)
public class RpcRequestMessage extends MyMessage{
private String interfaceName;
private String methodName;
private Class> returnType;
private Class>[] parameterTypes;
private Object[] parameterValues;
public RpcRequestMessage(String interfaceName, String methodName, Class> returnType, Class>[] parameterTypes, Object[] parameterValues) {
this.messageType=getMessageType();
this.interfaceName = interfaceName;
this.methodName = methodName;
this.returnType = returnType;
this.parameterTypes = parameterTypes;
this.parameterValues = parameterValues;
}
@Override
public int getMessageType() {
return MessageType.RpcRequestMessage;
}
}
Gson不支持Class类型的解析,所以使用Gson解析Class类型为字符串时要自定义编解码器!强烈建议使用Jackson,Gson坑太多
2.其次是Rpc响应信息体,服务器调用了对应的方法,需要给客户端返回返回值和异常信息。
public class RpcResponseMessage extends AbstractResponseMessage{
private Object returnValue;
private Exception exceptionValue;
public RpcResponseMessage(Object returnValue, Exception exceptionValue) {
this.messageType=getMessageType();
this.returnValue = returnValue;
this.exceptionValue = exceptionValue;
}
@Override
public int getMessageType() {
return MessageType.RpcResponseMessage;
}
}
3.在Netty服务器这边添加监听Rpc请求的Handler
这个Handler的代码部分就是关键了,这里我先写死!
@ChannelHandler.Sharable public class RpcRequestMessageHandler extends SimpleChannelInboundHandler{ @Override protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) { RpcResponseMessage res = new RpcResponseMessage(null, null); try { //这里先写死 if ("com.lxc.chatsystem.service.FriendsService".equals(msg.getInterfaceName())) { //获得该接口的实现类对象 FriendsService service = FriendsServiceFactory.getFriendsService(); //反射调用 Method method = service.getClass().getMethod(msg.getMethodName(), msg.getParameterTypes()); //得到返回结果 Object result = method.invoke(service,msg.getParameterValues()); System.out.println(result); res.setReturnValue(result); } else { System.out.println("错误"); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); //出异常的时候,要返回异常 res.setExceptionValue(e); } //返回响应对象 ctx.writeAndFlush(res); } }
4.同样的在Netty客户端这边加入Rpc调用的响应消息处理器,接收返回值和异常信息
打印出结果和异常即可
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler{ @Override protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception { Object returnValue = msg.getReturnValue(); Exception exceptionValue = msg.getExceptionValue(); System.out.println(returnValue); System.out.println(exceptionValue); } }
5.测试
使用我的服务器中的一个方法进行测试,service接口代码如下(接口的实现类忽略):
现在客户端想要使用该方法,我们就需要给服务器发送Rpc请求了,请求信息包含了我要调用服务器方法的一切信息:
结果正确,客户端正常执行到了服务端的方法,并得到了返回值。
RPC设计刚刚我们只是验证了客户端和服务器的通信部分,且方法也是写死了的,以后我们要想调用各种各样的方法当然是不行的,所以现在才真正的开始写RPC了。
1.同步接口我们的服务器和客户端日后肯定不在一个地方,但是我们的客户端需要方便的使用到服务器的方法,所以我们最起码也要让服务器和客户端的接口相同(也可以使用命令的方式调用,不需要同步接口),如下,我的客户端扣了服务器的接口。
2.创建代理客户端有了接口以后,就相当于有了一个模板,但是还是缺少实现类,为了能够像运行本机方法一样的运行服务器方法,我们现在还需要实现类,但是实现类当然不能扣服务器的,这样也就没有意义了,我们这时候就需要用到动态代理了,把这个复杂的方法调用过程给屏蔽起来。代码如下:
public class RpcProxyService {
public static T getProxyService(Class serviceClass) {
ClassLoader classLoader = serviceClass.getClassLoader();
Class>[] interfaces = {serviceClass};
//创建代理对象
Proxy proxy = (Proxy) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
//1.将方法调用的逻辑转为 发送rpc消息,这也是代理的重点
RpcRequestMessage rpcMessage = new RpcRequestMessage(serviceClass.getName(),
method.getName(),
method.getReturnType(),
method.getParameterTypes(),
args
);
//设置消息顺序号
rpcMessage.setSequenceId(SequenceIdGenerator.nextId());
//2.发送给服务器,我奥调用你的某个方法了
AppInfo.clientChannel.writeAndFlush(rpcMessage);
//3.准备一个promise用于接收结果,并放进存放结果的容器里等待 指定promise对象异步接收结果的线程
DefaultPromise
取顺序号代码如下,如果需要考虑分布式可以使用雪花算法,这里只是单机的序列号生成
public abstract class SequenceIdGenerator {
private static final AtomicInteger id=new AtomicInteger();
public static int nextId(){
return id.getAndIncrement();
}
}
Promise结果容器代码如下
public class PromiseContainer {
public static final Map> PROMISES_MAP =new ConcurrentHashMap<>();
}
3. 服务器接收和返回结果
服务器这边对应的handler如下,它的职责是接收到客户端的rpc请求,调用我们服务器的对应的方法,并返回结果
@ChannelHandler.Sharable public class RpcRequestMessageHandler extends SimpleChannelInboundHandler{ @Override protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) { //1.准备好返回值 RpcResponseMessage res = new RpcResponseMessage(null, null); //2.设置相同的序列号 res.setSequenceId(msg.getSequenceId()); try { //3.根据客户端传过来的接口名称字符串得到服务器的对应接口的Class类型,可以使用spring管理更方便 Class> serviceClass = ServiceFactory.getServiceClass(msg.getInterfaceName()); //4.反射调用对应的方法 Method method = serviceClass.getMethod(msg.getMethodName(), msg.getParameterTypes()); Object returnValue = method.invoke(serviceClass.newInstance(), msg.getParameterValues()); //5.设置返回值 System.out.println(returnValue); res.setReturnValue(returnValue); } catch (Exception e) { e.printStackTrace(); //出异常的时候,要返回异常 res.setExceptionValue(new RuntimeException("远程调用出错:" + e.getCause().getMessage())); } //返回响应对象给客户端 ctx.writeAndFlush(res); } }
其中的ServiceFactory如下:这里我们因为没有使用spring所有就根据业务自己写了个方法。用这种方法的话,客户端的接口和服务器的接口就不需要相同的路径,只需要最后一个接口名相同就可以识别出来。
public class ServiceFactory {
public static Class> getServiceClass(String serviceName){
int index = serviceName.lastIndexOf('.');
String simpleName = serviceName.substring(index + 1);
switch (simpleName) {
case "PersonService":
return PersonServiceImpl.class;
case "FriendsService":
return FriendsServiceImpl.class;
default:
throw new RuntimeException("没有此类型");
}
}
}
4.测试
现在我们就可以在客户端里发送rpc请求了。
如上是一个客户端请求rpc的代码片段,可以看出客户端已经完全对远程调用透明了,所有的事情都是代理类来帮我们做的(动态代理真是个好东西)。



