MyRPC版本1

RPC的概念

背景知识
  • RPC的基本概念,核心功能

image-20200805001037799

常见的RPC框架

Duboo基本功能
  1. 远程通讯
  2. 基于接口方法的透明远程过程调用
  3. 负载均衡
  4. 服务注册中心
RPC过程

client 调用远程方法-> request序列化 -> 协议编码 -> 网络传输-> 服务端 -> 反序列化request -> 调用本地方法得到response -> 序列化 ->编码->……


版本迭代过程

目录

从0开始的RPC的迭代过程:

  • version0版本:以不到百行的代码完成一个RPC例子
  • version1版本:完善通用消息格式(request,response),客户端的动态代理完成对request消息格式的封装
  • version2版本:支持服务端暴露多个服务接口, 服务端程序抽象化,规范化
  • version3版本:使用高性能网络框架netty的实现网络通信,以及客户端代码的重构
  • version4版本:自定义消息格式,支持多种序列化方式(java原生, json…)
  • version5版本: 服务器注册与发现的实现,zookeeper作为注册中心
  • version6版本: 负载均衡的策略的实现

1.MyRPC版本1

背景知识
  • 反射
  • 动态代理
本节问题
  • 如何使客户端请求远程方法支持多种?

  • 如何使服务端返回值的类型多样?

版本升级过程

更新1:定义了一个通用的Request的对象(消息格式)

/**
 * 在上个例子中,我们的Request仅仅只发送了一个id参数过去,这显然是不合理的,
 * 因为服务端不会只有一个服务一个方法,因此只传递参数不会知道调用那个方法
 * 因此一个RPC请求中,client发送应该是需要调用的Service接口名,方法名,参数,参数类型
 * 这样服务端就能根据这些信息根据反射调用相应的方法
 * 还是使用java自带的序列化方式
 */
@Data
@Builder
public class RPCRequest implements Serializable {
    // 服务类名,客户端只知道接口名,在服务端中用接口名指向实现类
    private String interfaceName;
    // 方法名
    private String methodName;
    // 参数列表
    private Object[] params;
    // 参数类型
    private Class<?>[] paramsTypes;
}

更新2: 定义了一个通用的Response的对象(消息格式)

/**
 * 上个例子中response传输的是User对象,显然在一个应用中我们不可能只传输一种类型的数据
 * 由此我们将传输对象抽象成为Object
 * Rpc需要经过网络传输,有可能失败,类似于http,引入状态码和状态信息表示服务调用成功还是失败
 */
@Data
@Builder
public class RPCResponse implements Serializable {
    // 状态信息
    private int code;
    private String message;
    // 具体数据
    private Object data;
    public static RPCResponse success(Object data) {
        return RPCResponse.builder().code(200).data(data).build();
    }
    public static RPCResponse fail() {
        return RPCResponse.builder().code(500).message("服务器发生错误").build();
    }
}

因此在网络传输过程都是request与response格式的数据了,客户端与服务器端就要负责封装与解析以上结构数据

更新3: 服务端接受request请求,并调用request中的对应的方法

public interface UserService {
    // 客户端通过这个接口调用服务端的实现类
    User getUserByUserId(Integer id);
    // 给这个服务增加一个功能
    Integer insertUserId(User user);
}

服务端的实现类UserServiceImpl要实现增加的功能

@Override
public Integer insertUserId(User user) {
    System.out.println("插入数据成功:"+user);
    return 1;
}

服务端接受解析reuqest与封装发送response对象

public class RPCServer {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        try {
            ServerSocket serverSocket = new ServerSocket(8899);
            System.out.println("服务端启动了");
            // BIO的方式监听Socket
            while (true){
                Socket socket = serverSocket.accept();
                // 开启一个线程去处理,这个类负责的功能太复杂,以后代码重构中,这部分功能要分离出来
                new Thread(()->{
                    try {
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                        // 读取客户端传过来的request
                        RPCRequest request = (RPCRequest) ois.readObject();
                        // 反射调用对应方法
                        Method method = userService.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
                        Object invoke = method.invoke(userService, request.getParams());
                        // 封装,写入response对象
                        oos.writeObject(RPCResponse.success(invoke));
                        oos.flush();
                    }catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e){
                        e.printStackTrace();
                        System.out.println("从IO中读取数据错误");
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败");
        }
    }
}

更新4: 客户端根据不同的Service进行动态代理:

代理对象增强的公共行为:把不同的Service方法封装成统一的Request对象格式,并且建立与Server的通信

  1. 底层的通信
public class IOClient {
    // 这里负责底层与服务端的通信,发送的Request,接受的是Response对象
    // 客户端发起一次请求调用,Socket建立连接,发起请求Request,得到响应Response
    // 这里的request是封装好的(上层进行封装),不同的service需要进行不同的封装, 客户端只知道Service接口,需要一层动态代理根据反射封装不同的Service
    public static RPCResponse sendRequest(String host, int port, RPCRequest request){
        try {
            Socket socket = new Socket(host, port);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            System.out.println(request);
            objectOutputStream.writeObject(request);
            objectOutputStream.flush();
            RPCResponse response = (RPCResponse) objectInputStream.readObject();
            return response;
        } catch (IOException | ClassNotFoundException e) {
            System.out.println();
            return null;
        }
    }
}
  1. 动态代理封装request对象
@AllArgsConstructor
public class ClientProxy implements InvocationHandler {
    // 传入参数Service接口的class对象,反射封装成一个request
    private String host;
    private int port;
    // jdk 动态代理, 每一次代理对象调用方法,会经过此方法增强(反射获取request对象,socket发送至客户端)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // request的构建,使用了lombok中的builder,代码简洁
        RPCRequest request = RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .params(args).paramsTypes(method.getParameterTypes()).build();
        // 数据传输
        RPCResponse response = IOClient.sendRequest(host, port, request);
        //System.out.println(response);
        return response.getData();
    }
    <T>T getProxy(Class<T> clazz){
        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
        return (T)o;
    }
}
  1. 客户端调用不同的方法
public class RPCClient {
    public static void main(String[] args) {
        ClientProxy clientProxy = new ClientProxy("127.0.0.1", 8899);
        UserService proxy = clientProxy.getProxy(UserService.class);
        // 服务的方法1
        User userByUserId = proxy.getUserByUserId(10);
        System.out.println("从服务端得到的user为:" + userByUserId);
        // 服务的方法2
        User user = User.builder().userName("张三").id(100).sex(true).build();
        Integer integer = proxy.insertUserId(user);
        System.out.println("向服务端插入数据:"+integer);
    }
}
结果

image-20200805163937195

image-20200805163959630

总结
  1. 定义更加通用的消息格式:Request 与Response格式, 从此可能调用不同的方法,与返回各种类型的数据。
  2. 使用了动态代理进行不同服务方法的Request的封装,
  3. 客户端更加松耦合,不再与特定的Service,host,port绑定
存在的痛点
  1. 服务端我们直接绑定的是UserService服务,如果还有其它服务接口暴露呢?(多个服务的注册)
  2. 服务端以BIO的方式性能是否太低,
  3. 服务端功能太复杂:监听,处理。需要松耦合

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值