自己动手实现RPC框架

RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务

源码: https://github.com/wantao666/jrpc
参考视频: 自己动手实现RPC框架

1.跨进程数据交换

1.1 依赖中间件做数据交互

在这里插入图片描述
优点: 解耦合,异步
缺点: 系统复杂性提高,可用性降低

2.1 直接交互

在这里插入图片描述
优点: 系统复杂性低,速度快
缺点: 耦合度高,同步

http和rpc的区别:

相同点:
    都是基于socket通信,都可以实现远程调用
不同点:
    rpc: 自定义tcp协议,报文更小,调用快,处理快。
    http: 通用性更强,实现较为复杂
总结: 如果局域网内服务的调用,最好使用rpc,因为这样更快。如果需要提供对外的环境,如浏览器调用、APP调用、第三方服务调用,则最好使用http

2.RPC架构

在这里插入图片描述
服务提供者(RPC Server): 运行在服务器端,提供服务接口定义与服务实现类。

服务中心(Registry): 运行在服务器端,负责将本地服务发布成远程服务,管理远程服务,提供给服务消费者使用。

服务消费者(RPC Client): 运行在客户端,通过远程代理对象调用远程服务。

3.现有RPC框架对比

在这里插入图片描述

4.自己动手实现RPC框架

在这里插入图片描述

核心主要有5个主要的模块组成.

  1. 协议模块
  2. 序列化模块
  3. 网络传输模块
  4. 服务端模块
  5. 客户端模块
4.0 共同模块(common)

提供反射工具类
ReflectionUtils.java

/**
 * 反射工具类
 */
public class ReflectionUtils {

    //根据类clazz创建对象
    public static <T> T newInstance(Class<T> clazz) {
        T instance = null;
        try {
            instance = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    //获得该类自身声明的所有public方法
    public static Method[] getPublicMethods(Class clazz) {
        List<Method> pMethods = new ArrayList<>();
        //返回该类声明的所有方法
        Method[] methods = clazz.getDeclaredMethods();
        //过滤到public方法
        pMethods = Arrays
                .stream(methods)
                .filter(method ->
                        Modifier.isPublic(method.getModifiers())
                ).collect(Collectors.toList());
        return pMethods.toArray(new Method[0]);
    }

    //调用指定对象的指定方法
    public static Object invoke(Object obj, Method method, Object... args) {
        Object object = null;
        try {
            object = method.invoke(obj, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}
4.1 协议模块(proto)

主要是封装了RPC的请求,响应和服务描述类。

ServiceDescriptor.java

public class ServiceDescriptor {
    private String clazz;
    private String method;
    private String returnType;
    private String[] paramTypes;
 }

Request.java

public class Request {
    private ServiceDescriptor service;
    private Object[] params;
}

Response.java

public class Response {
    private Integer code = 0; // 0 success
    private String msg = "success";
    private Object data;
}
4.2 序列化模块(codec)

使用fastjson,基于json进行序列化和反序列化,将java对象转为json形式进行网络传输,接受到json后再转为java对象。
JsonEncoder.java

/**
 * 基于Json的序列化
 */
public class JsonEncoder implements Encoder {
    @Override
    public byte[] encode(Object obj) {
        return JSON.toJSONBytes(obj);
    }
}

JsonDecoder.java

/**
 * 基于Json的反序列化
 */
public class JsonDecoder implements Decoder {
    @Override
    public <T> T decode(byte[] bytes, Class<T> clazz) {
        return JSON.parseObject(bytes, clazz);
    }
}
4.3 网络传输模块(transport)

客户端: 基于java原生的HttpURLConnection,建立短连接。
HttpTransportClient.java

/**
 * 基于Http(HttpURLConnection)的客户端:短连接
 */
@Slf4j
public class HttpTransportClient implements TransportClient {
    private String url;

    @Override
    public void connect(Peer peer) {
        this.url = "http://" + peer.getHost() + ":" + peer.getPort();
    }

    @Override
    public InputStream write(InputStream data) {
        try {
            log.info("URL:" + this.url);
            HttpURLConnection connection = (HttpURLConnection) new URL(this.url).openConnection();
            //是否能够向connection中输入,如发送post请求,默认是false
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestMethod("POST");
            connection.connect();
            //将data写入connection
            IOUtils.copy(data, connection.getOutputStream());

            int code = connection.getResponseCode();
            if (code == HttpURLConnection.HTTP_OK) {
                return connection.getInputStream();
            } else {
                return connection.getErrorStream();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void close() {
    }
}

服务器端: 基于jetty,构建嵌入式web服务器。
HttpTransportServer.java

/**
 * 基于http(jetty)的服务器端
 */
@Slf4j
public class HttpTransportServer implements TransportServer {
    private Server server;
    private RequestHandler handler;

    @Override
    public void init(int port, RequestHandler handler) {
        this.server = new Server(port);
        this.handler = handler;

        //servlet接收请求
        ServletContextHandler ctx = new ServletContextHandler();
        server.setHandler(ctx);

        //ServletHolder:网络请求抽象
        ServletHolder holder = new ServletHolder(new RequestServlet());
        ctx.addServlet(holder, "/*");
    }

    @Override
    public void start() {
        try {
            server.start();
            //让server一直挂起
            server.join();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void stop() {
        try {
            server.stop();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(), e);
        }
    }

    class RequestServlet extends HttpServlet {
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            log.info("get request success");
            ServletInputStream in = req.getInputStream();
            ServletOutputStream out = resp.getOutputStream();

            if (handler != null) {
                handler.onRequest(in, out);
            }
            out.flush();
        }
    }
}
4.4 服务端模块(server)

ServiceManager:以ConcurentHashMap作为容器,提供服务注册和服务查找功能。

ServiceManager.java

@Slf4j
public class ServiceManager {
    private Map<ServiceDescriptor, ServiceInstance> services;

    public ServiceManager() {
        this.services = new ConcurrentHashMap<>();
    }

    //注册服务
    public <T> void register(Class<T> interfaceClass, T bean) {
        Method[] methods = ReflectionUtils.getPublicMethods(interfaceClass);
        for (Method method : methods) {
            ServiceInstance serviceInstance = new ServiceInstance(bean, method);
            ServiceDescriptor sdp = ServiceDescriptor.from(interfaceClass, method);
            services.put(sdp, serviceInstance);
            log.info("register service: {}:{}", sdp.getClazz(), sdp.getMethod());
        }
    }

    //查找服务
    public ServiceInstance lookup(Request request) {
        ServiceDescriptor sdp = request.getService();
        return services.get(sdp);
    }
}

ServiceInvoker:通过动态代理,调用具体服务
ServiceInvoker.java

/**
 * 调用具体服务
 */
public class ServiceInvoker {
    public Object invoke(ServiceInstance service, Request request) {
        return ReflectionUtils.invoke(service.getTarget(), service.getMethod(), request.getParams());
    }
}
4.5 客户端模块(server)

RemoteInvoker: 使用jdk默认的动态代理,通过代理对象,进程远程方法调用。
RemoteInvoker.java

/**
 * 调用远程服务的代理类
 */
@Slf4j
public class RemoteInvoker implements InvocationHandler {
    private Class clazz;
    private Encoder encoder;
    private Decoder decoder;
    private TransportSelector selector;

    RemoteInvoker(Class clazz, Encoder encoder, Decoder decoder, TransportSelector selector) {
        this.clazz = clazz;
        this.encoder = encoder;
        this.decoder = decoder;
        this.selector = selector;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Request request = new Request();
        request.setService(ServiceDescriptor.from(clazz, method));
        request.setParams(args);

        Response response = invokeRemote(request);
        if (response == null || response.getCode() != 0) {
            throw new IllegalStateException("fail to invoke remote: " + response);
        }
        return response.getData();
    }

    private Response invokeRemote(Request request) {
        TransportClient client = null;
        Response response = null;
        try {
            client = selector.select();
            byte[] bytes = encoder.encode(request);
            InputStream in = client.write(new ByteArrayInputStream(bytes));
            byte[] inBytes = IOUtils.readFully(in, in.available(), true);
            response = decoder.decode(inBytes, Response.class);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            response.setCode(-1);
            response.setMsg("RpcClient got error:" + e.getClass() + ":" + e.getMessage());
        } finally {
            if (client != null) {
                selector.release(client);
            }
        }
        return response;
    }
}
4.6 使用案例模块(example)

eg:使用rpc完成add方法的调用

调用接口和具体实现:

public interface CalcService {
    int add(int a,int b);
}
public class CalcServiceImpl implements CalcService {
    @Override
    public int add(int a, int b) {
        return a+b;
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        RpcClient client = new RpcClient();
        CalcService service = client.getProxy(CalcService.class);
        int result = service.add(1, 2);
        System.out.println(result);
    }
}

服务器端:

public class Server {
    public static void main(String[] args) {
        RpcServer server = new RpcServer();
        server.register(CalcService.class, new CalcServiceImpl());
        server.start();
    }
}

执行结果:
在这里插入图片描述

源码: https://github.com/wantao666/jrpc

参考视频: 自己动手实现RPC框架

  • 14
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
### 回答1: 实现简单的RPC框架需要遵循以下步骤: 1. 定义接口:首先需要定义一个接口,其中包含所有可远程调用的方法。 2. 服务端实现:服务端需要实现定义的接口,并通过服务端的程序来向客户端提供远程访问的服务。 3. 客户端实现:客户端需要消息代理,用于将请求发送到服务端并接收服务端的响应。 4. 网络通信:需要在服务端和客户端之间进行网络通信,以传递请求和响应。 Java提供了很多用于网络通信的工具,例如Java RMI和Java Sockets,可以使用它们来实现RPC框架。 参考代码: ``` import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.ServerSocket; import java.net.Socket; public class RpcFramework { /** * 暴露服务 * * @param service 服务实现 * @param port 服务端口 * @throws Exception */ public static void export(final Object service, int port) throws Exception { if (service == null) throw new IllegalArgumentException("service instance == null"); if (port <= 0 || port > 65535) throw new IllegalArgumentException("Invalid port " + port); System.out.println("Export service " + service.getClass().getName() + " on port " + port); ServerSocket server = new ServerSocket(port); for (; ; ) { try { final Socket socket = server.accept(); new Thread(() -> { try { try { ObjectInputStream input = new ObjectInputStream(socket.getInputStream ### 回答2: 简单RPC(远程过程调用)框架是用于实现分布式系统通信的技术。使用Java编程语言可以很方便地实现一个简单的RPC框架。下面是使用Java实现简单RPC框架的步骤: 1. 定义接口:首先,需要定义服务接口,描述需要远程调用的方法。接口中定义了需要远程调用的方法以及方法的参数和返回值。 2. 实现接口:编写实现类,实现定义的接口。这个实现类通常是服务提供者的角色,用于处理并提供远程调用所需的服务。 3. 服务注册:创建一个服务注册中心,用于维护服务提供者的信息。服务提供者在启动的时候,向注册中心注册自己提供的服务。 4. 远程调用:服务消费者需要进行远程调用时,首先需要到注册中心查询可用的服务提供者,并选择其中一个进行调用。 5. 网络通信:服务消费者通过网络与选择的服务提供者建立通信连接,发送请求,并等待响应。可以使用Java的Socket或HTTP等通信协议进行数据传输。 6. 序列化与反序列化:为了将Java对象在网络中传输,需要将对象进行序列化成字节流进行传输,并在接收端将字节流反序列化为对象。可以使用Java提供的对象流或第三方库进行序列化和反序列化操作。 7. 执行远程方法:服务提供者接收到网络请求后,根据请求的方法名和参数,执行对应的方法,并将结果序列化后返回给服务消费者。 8. 异常处理:在远程调用过程中可能会出现各种异常,需要在框架中考虑异常的处理方式,比如将异常信息序列化后返回给调用方。 通过以上步骤,使用Java就可以实现一个简单的RPC框架。在实际应用中,可以根据需求进行优化和扩展,比如支持负载均衡、服务发现、容错机制等,以满足分布式系统的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Selenium399

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值