从头开始写一个简单RPC——第0天

gitee地址

项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork

先简单介绍下最开始这个RPC的使用方式:

服务端发布服务:

public static void main(String[] args) throws Exception {
		// 提供一个名字叫做 hello 的远程服务,服务实例是 HelloService
       ServiceProvider.publishService("hello", new HelloService());
       //使用netty 启动RPC 服务
       NettyRpcServer server = new NettyRpcServer();
       server.start();
}

客户端调用服务:

public static void main(String[] args) {
		// 调用服务名为 hello 的远程服务,参数是 miemie
        Object hello = ServiceConsumer.invoke("hello", "miemie");
        System.out.println(hello);
}

先基于这种最简单的实现方式,通过手动填写远程服务名,来发布和调用远程服务,后续会进行改造,模拟dubbo 实现方式,基于注解自动发布和调用服务。

简单的原理说明

基本流程:

  1. 服务生产者向注册中心提供以下信息: 服务的名称、服务地址和服务端口,这些信息将保存在注册中心。
  2. 服务消费者拿着服务名称,向注册中心询问,这个远程服务有哪些机器在提供,注册中心向消费者返回服务地址和服务端口。
  3. 消费者这下知道了要向哪台生产者建立连接,连接建立完成之后,向生产者发送一条数据:服务名和参数,表示要调用该远程服务。
  4. 生产者收到服务名和参数,通过服务名查找本地存在的服务实例,然后进行调用,并向消费者返回结果。
介绍用到的组件

组件介绍

consume
	public class ServiceConsumer {

    private static ServiceRegister serviceRegister = new LocalFileServiceRegister();
    private static NettyRpcClient nettyRpcClient = NettyRpcClient.getNettyRpcClient();

    public static Object invoke(String serviceName, Object ...params) {
        InetSocketAddress address = serviceRegister.lookup(serviceName);
        //发送参数,调用服务
        CompletableFuture<RpcResponse> result = nettyRpcClient.sendRequest(address, serviceName, params);
        try {
            return result.get(15, TimeUnit.SECONDS).getData();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } catch (TimeoutException e) {
            e.printStackTrace();
            return null;
        } catch (ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }
}

在基本流程第2 步中说到:服务消费者拿着服务名称,向注册中心询问,这个远程服务有哪些机器在提供,注册中心向消费者返回服务地址和服务端口。
因此,自然而然可以让 ServiceConsumer 持有一个 注册中心的引用 serviceRegister;
又如基本流程第3 步中说到:消费者这下向生产者建立连接,并发送服务名和参数,表示要调用该远程服务。
在这里我们用 Netty 替我们完成这项工作。

provider
public class ServiceProvider {

    private static final ConcurrentHashMap<String, RemoteService> servicesMap = new ConcurrentHashMap<String, RemoteService>();
    private static final ServiceRegister serviceRegister = new LocalFileServiceRegister();

    //发布服务的方法
    public static void publishService(String serviceName, RemoteService remoteService) {
        servicesMap.put(serviceName, remoteService);
        serviceRegister.regist(serviceName);
    }

    //查找服务的方法
    public static RemoteService find(String serviceName) {
        return servicesMap.get(serviceName);
    }
}

在基本流程第1 步中说到,生产者向注册中心提供信息,由注册中心保存,因此生产者 **ServiceProvider ** 可以持有一个注册中心的引用。
在基本流程第4 步中说到,生产者通过服务名查找本地服务实例,这里用HashMap 来实现,key 是服务名称,value 是服务实例。

regist
public interface ServiceRegister {
    void regist(String serviceName);

    InetSocketAddress lookup(String serviceName);
}

/**
* 本地文件实现方式
*/
public class LocalFileServiceRegister implements ServiceRegister{
    private static final String PATH = "register/dir";
    private static final File dir = new File(PATH);

    @Override
    public void regist(String serviceName) {
        if(!dir.exists()) {
            dir.mkdirs();
        }
        File service = new File(PATH + "/" + serviceName);
        service.deleteOnExit();
        try {
            service.createNewFile();
            FileWriter fw = new FileWriter(service);
            fw.write(InetAddress.getLocalHost().getHostAddress() + "@@" + NettyRpcServer.PORT);
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public InetSocketAddress lookup(String serviceName) {
        File service = new File(PATH + "/" + serviceName);
        if(!service.exists()) {
            return null;
        }
        try {
            BufferedReader br = new BufferedReader(new FileReader(service));
            String ipPort = br.readLine();
            String[] split = ipPort.split("@@");
            return new InetSocketAddress(split[0], Integer.parseInt(split[1]));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

通过上面的基本流程,我们知道,注册中心至少要提供两个功能:服务注册和服务发现。
这里为了实现方便,先使用本地文件作为注册中心,后续改为zookeeper。

transport

传输层,我觉得是RPC 的核心,这里我使用Netty 作为生产者和消费者通信的桥梁。
编解码
在这里,简单实现,我们使用Java 序列化,后续这一块作为性能优化点,可以使用别的序列化方式。
请求响应
我们将请求和响应封装成:

//rpc 请求
public class RpcRequest implements Serializable {
    private static final long serialVersionUID = 1905122041950251207L;
    private String requestId;
    private String serviceName;
    private Object[] parameters;
    private Class<?>[] paramTypes;

}

// rpc 响应
public class RpcResponse<T> implements Serializable {

    private static final long serialVersionUID = 715745410605631233L;
    private String requestId;
    /**
     * response code
     */
    private Integer code;
    /**
     * response message
     */
    private String message;
    /**
     * response body
     */
    private T data;

    public static <T> RpcResponse<T> success(T data, String requestId) {
        RpcResponse<T> response = new RpcResponse<>();
        response.setCode(200);
        response.setMessage("remote success");
        response.setRequestId(requestId);
        if (null != data) {
            response.setData(data);
        }
        return response;
    }

    public static <T> RpcResponse<T> fail(int code) {
        RpcResponse<T> response = new RpcResponse<>();
        response.setCode(code);
        response.setMessage("fail");
        return response;
    }

我们先看看客户端调用流程:
ServiceConsumer.invoke(…) 中,调用 NettyRpcClient 的 sendRequest(…) 方法, 这里用到了 CompletableFuture 接收返回结果,具体使用参考:CompletableFuture 使用

public CompletableFuture sendRequest(InetSocketAddress address, String serviceName, Object[] params) {
        Class[] paramTypes = null;
        if(params != null && params.length > 0) {
            paramTypes = new Class[params.length];
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];
                paramTypes[i] = param.getClass();
            }
        }

        // build return value
        CompletableFuture<RpcResponse> resultFuture = new CompletableFuture<>();
        //获取连接
        Channel channel = getChannel(address);
        RpcRequest rpcRequest = RpcRequest.builder()
                .requestId(UUID.randomUUID().toString())
                .parameters(params)
                .serviceName(serviceName)
                .paramTypes(paramTypes)
                .build();
        unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);
        channel.writeAndFlush(rpcRequest).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                log.info("client send message: [{}]", rpcRequest);
            } else {
                future.channel().close();
                resultFuture.completeExceptionally(future.cause());
                log.error("Send failed:", future.cause());
            }
        });
        return resultFuture;
    }

这里传入向注册中心获取的生产者地址建立连接通道(这里有个优化点,channel 没有复用,每次调用都新建连接);

Channel channel = getChannel(address);


@SneakyThrows
private Channel getChannel(InetSocketAddress address) {
      CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
      bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
          if (future.isSuccess()) {
              log.info("The client has connected [{}] successful!", address.toString());
              completableFuture.complete(future.channel());
          } else {
              throw new IllegalStateException();
          }
      });
      return completableFuture.get();
}

然后构建RpcRequest,发送给生产者

channel.writeAndFlush(rpcRequest).addListener(....)

当生产者返回数据时,会触发NettyRpcClientHandler 的channelRead(…) 方法,到此就完成的远程服务的调用。

public class NettyRpcClientHandler extends ChannelInboundHandlerAdapter {
	...
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            log.info("client receive msg: [{}]", msg);
            if (msg instanceof RpcResponse) {
                RpcResponse rpcResponse = (RpcResponse) msg;
                unprocessedRequests.complete(rpcResponse);
            }
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
    ...
}

接下来看看服务端的执行流程:
当生产者收到消费者的请求时,会触发NettyRpcServerHandler 的channelRead(…) 方法:

public class NettyRpcServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            if (msg instanceof RpcRequest) {
                log.info("server receive msg: [{}] ", msg);
                RpcRequest rpcRequest = (RpcRequest) msg;
                // Execute the target method (the method the client needs to execute) and return the method result
                Object result = rpcRequestHandler.handle(rpcRequest);
                log.info(String.format("server get result: %s", result.toString()));
                RpcResponse rpcResponse = null;
                if (ctx.channel().isActive() && ctx.channel().isWritable() && ! (result instanceof Exception)) {
                    rpcResponse = RpcResponse.success(result, rpcRequest.getRequestId());
                } else {
                    rpcResponse = RpcResponse.fail(500);
                    log.error("not writable now, message dropped");
                }
                ctx.writeAndFlush(rpcResponse).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        } finally {
            //Ensure that ByteBuf is released, otherwise there may be memory leaks
            ReferenceCountUtil.release(msg);
        }
    }
}

然后查找指定的服务实例,执行调用:

public class RpcRequestHandler {

    /**
     * Processing rpcRequest: call the corresponding method, and then return the method
     */
    public Object handle(RpcRequest rpcRequest) {
        RemoteService service = ServiceProvider.find(rpcRequest.getServiceName());
        if(service == null) {
            return new RemoteException("not find service");
        }
        return invokeTargetMethod(rpcRequest, service);
    }

    /**
     * get method execution results
     *
     * @param rpcRequest client request
     * @param service    service object
     * @return the result of the target method execution
     */
    private Object invokeTargetMethod(RpcRequest rpcRequest, RemoteService service) {
        return service.invoke(rpcRequest.getParameters());
    }
}
接下来要优化的点

1、解决channel 复用问题
2、目前服务实例必须实现 RemoteService 接口,不够灵活
3、不适用java 自身的序列化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值