手写RPC笔记

RPC即远程过程调用,也叫远程方法调用,RPC框架可以实现调用方可以像调用本地方法一样调用远程服务的方法。

步骤思路:

1、想要在Consumer中调用Provider中sayHello方法,文件组织如下:

Provider-common模块:定义暴露接口供提供者和消费者使用(Consumer不希望直接使用Provider的实现类,而只是使用其接口进行调用),然后让Consumer和Provider依赖于Provider-common包。

java 复制代码
public interface HelloService {
    String sayHello(String name);
}
复制代码
Provider实现HelloServiceImpl实现接口
java 复制代码
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello: " + name;
    }
}

Consumer指定实现类并执行方法调用:

java 复制代码
public class Comsumer {
    public static void main(String[] args) {
        HelloService helloService = ?;
        String result = helloService.sayHello("test");
}}

现在想做的事情就是Consumer能够调用到Provider中的实现类方法HelloServiceImpl

2、实现思路:在Provider启动时首先将自己要开放的接口本地注册到RPC,然后调用RPC server 中start方法开启Tomcat接受网络请求,然后由RPC servlet解析req得到接口名、调用方法、参数类型、方法参数等信息,然后通过接口注册信息找到相应接口,然后通过反射执行此接口方法并将结果封装到resp并返回。

Provider类:

java 复制代码
public class Provider {
    public static void main(String[] args) {
        //将提供的服务注册进行本地注册以便后续进行反射调用

        LocalRegister.regist(HelloService.class.getName(), "1.0", HelloServiceImpl.class);

        // 启动Netty、Tomcat服务
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(), url.getPort());

    }
}
复制代码
RPC模块:
HttpServer类:负责接收处理请求并返回结果
java 复制代码
public class HttpServer {
    public void start(String hostname, Integer port){
        // 读取用户的配置 server.name=netty
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);

        Host host = new StandardHost();
        host.setName(hostname);

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

}

public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        new HttpServerHandler().handler(req, resp);
    }
}

public class HttpServerHandler {

    public void handler(HttpServletRequest req, HttpServletResponse resp){
        // 处理请求-->接口、方法、方法参数
        try {
            Invocation invocation = (Invocation) new ObjectInputStream(req.getInputStream()).readObject();
            String interfaceName = invocation.getInterfaceName();

            Class classImpl = LocalRegister.get(interfaceName, "1.0");
            Method method = classImpl.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
            String result = (String) method.invoke(classImpl.newInstance(), invocation.getParameters());

            IOUtils.write(result, resp.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

Invocation类:传递参数包装类

java 复制代码
public class Invocation implements Serializable {

    private String interfaceName;
    private String methodName;
    private Class[] parameterTypes;
    private Object[] parameters;

    public Invocation(String interfaceName, String methodName, Class[] parameterTypes, Object[] parameters) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.parameterTypes = parameterTypes;
        this.parameters = parameters;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

LocalRegister类:保存接口注册信息

java 复制代码
public class LocalRegister {

    private static Map<String, Class> map = new HashMap<>();

    public static void regist(String interfaceName, String version, Class implClass){
        map.put(interfaceName+version, implClass);
    }

    public static Class get(String interfaceName, String version) {
        return map.get(interfaceName+version);
    }
}

3、Consumer启动时调用RPC client 发送http网络请求传递接口名、方法、参数信息,并堵塞等待,RPC server收到请求处理,然后得到返回结果实现调用,这就实现了RPC的基本功能。

Consumer:

java 复制代码
public class Consumer {
    public static void main(String[] args) {

        Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Class[]{String.class}, new Object[]{"lizhihui"});
        HttpClient httpClient = new HttpClient();
        try {
            String result = httpClient.send("localhost", 8080, invocation);
            System.out.println("result = " + result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

4、但是有个问题,这种调用方式不是我们希望达到的效果(即,通过实例化对象然后执行方法进行调用),我们就需要得到一个具体的对象。怎么得到?解决方法:让RPC能够根据接口产生并返回接口代理对象。

复制代码
Comsumer启动方法:
java 复制代码
public class Comsumer {
    public static void main(String[] args) {
        HelloService helloService = ProxyFactory.getProxy(HelloService.class);
        String result = helloService.sayHello("zhouyu123123123");
        System.out.println(result);


//        Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Class[]{String.class}, new Object[]{"lizhihui"});
//        HttpClient httpClient = new HttpClient();
//        try {
//            String result = httpClient.send("localhost", 8080, invocation);
//            System.out.println("result = " + result);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }

    }
}

ProxyFactory类:

java 复制代码
public class ProxyFactory {

    public static <T> T getProxy(Class interfaceClass) {
        // 用户配置

        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),
                        method.getParameterTypes(), args);
 
                HttpClient httpClient = new HttpClient();
                String result = httpClient.send("localhost", 8080, invocation);

                return result;

            }
        });

        return (T) proxyInstance;
    }
}

5、地址和端口号硬编码问题,"localhost"和8080为Provider的ip地址和端口号,如果我们能确定Provider应用的ip地址和端口号就能够解决这个问题。解决方法:注册中心,服务提供者Provider需要将自己的ip地址和端口号存到注册中心(Map实现)上去。服务消费者从注册中心拿到此ip地址和端口号就能解决这个问题。

URL类:

java 复制代码
public class URL implements Serializable {

    private String hostname;
    private Integer port;

    public URL(String hostname, Integer port) {
        this.hostname = hostname;
        this.port = port;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

注册中心:(注册中心三要素:注意这里是不能实现的,因为Provider和Consumer是两个不同的线程,Provider存的Map是不能被Consumer取到的,所以需要redis、zookeeper等进行共享 数据。此外,注册中心还需要有能力(心跳机制)监听 挂掉的服务 从而下线其注册数据。此外,消费者为了不是每一次都到注册中心读取数据影响速度,服务消费者还需要本地缓存,这就会引入本地缓存和注册中心之间的数据一致性问题,注册中心需要有数据变更的监听机制。)为了解决共享问题们这里采用简化方法,保存到本地文件进行共享的方式(savfile和getfile方法)。

java 复制代码
public class MapRemoteRegister {

    private static Map<String, List<URL>> map = new HashMap<>();

    public static void regist(String interfaceName, URL url){
        List<URL> list  = map.get(interfaceName);
        if (list == null) {
            list = new ArrayList<>();
        }
        list.add(url);

        map.put(interfaceName, list);

        saveFile();
    }

    public static List<URL> get(String interfaceName) {
        map = getFile();

        return map.get(interfaceName);
    }

    private static void saveFile() {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("/temp.txt");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(map);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Map<String, List<URL>> getFile() {
        try {
            FileInputStream fileInputStream = new FileInputStream("/temp.txt");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            return (Map<String, List<URL>>) objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

Provider类:

java 复制代码
public class Provider {

    public static void main(String[] args) {

        LocalRegister.regist(HelloService.class.getName(), "1.0", HelloServiceImpl.class);

        // 注册中心注册 服务注册
        URL url = new URL("localhost", 8080);
        MapRemoteRegister.regist(HelloService.class.getName(), url);


        // Netty、Tomcat
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(), url.getPort());
    }
}

ProxyFactory类:

java 复制代码
public class ProxyFactory {

    public static <T> T getProxy(Class interfaceClass) {
        // 用户配置

        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),
                        method.getParameterTypes(), args);

                HttpClient httpClient = new HttpClient();

                // 服务发现
                List<URL> list = MapRemoteRegister.get(interfaceClass.getName());
                //负载均衡
                URL url = Loadbalance.random(list);

                // 服务调用
                String result = httpClient.send(url.getHostname(), url.getPort(), invocation);

                return result;

            }
        });

        return (T) proxyInstance;
    }
}

这里获得了List<URL>是一个列表,我们需要选择一个rul进行服务调用,这就需要负载均衡,这里采用随机算法。

Loadbalance类:

java 复制代码
public class Loadbalance {

    public static URL random(List<URL> urls){
        Random random = new Random();
        int i = random.nextInt(urls.size());
        return urls.get(i);
    }
}

6、封装Provider启动类 Bootstrap 静态类

java 复制代码
public class Provider {
    public static void main(String[] args) {

//        LocalRegister.regist(HelloService.class.getName(), "1.0", HelloServiceImpl.class);
//
//        // 注册中心注册 服务注册
//        URL url = new URL("localhost", 8080);
//        MapRemoteRegister.regist(HelloService.class.getName(), url);
//
//
//        // Netty、Tomcat
//        HttpServer httpServer = new HttpServer();
//        httpServer.start(url.getHostname(), url.getPort());
        Bootstrap.start(
                HelloService.class.getName(), // 服务接口名称
                "1.0",                       // 服务版本号
                HelloServiceImpl.class,      // 服务实现类
                "localhost",                 // 主机名
                8080                         // 端口号
        );
    }
}

class Bootstrap {
    // 启动服务的静态方法,接收参数
    public static void start(String serviceName, String version, Class<?> serviceImplClass, String hostname, int port) {
        // 1. 本地注册服务
        registerLocalService(serviceName, version, serviceImplClass);

        // 2. 远程注册服务
        URL serviceUrl = registerRemoteService(serviceName, hostname, port);

        // 3. 启动 HTTP 服务器
        startHttpServer(serviceUrl);
    }

    // 本地注册服务
    private static void registerLocalService(String serviceName, String version, Class<?> serviceImplClass) {
        LocalRegister.regist(serviceName, version, serviceImplClass);
        System.out.println("Service registered locally: " + serviceName + " (Version: " + version + ")");
    }

    // 远程注册服务
    private static URL registerRemoteService(String serviceName, String hostname, int port) {
        URL url = new URL(hostname, port);
        MapRemoteRegister.regist(serviceName, url);
        System.out.println("Service registered remotely at: " + url);
        return url;
    }

    // 启动 HTTP 服务器
    private static void startHttpServer(URL url) {
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(), url.getPort());
        System.out.println("HTTP server started on " + url.getHostname() + ":" + url.getPort());
    }
}

6、服务调用时的服务容错(假设Provider未启动,Consumer先启动肯定会报错,我们可以写一些额外的逻辑,例如调用别的方法、记录日志等)

ProxyFactory类:修改 ProxyFactory 以接收 ErrorCallback 参数 : 我们将 ErrorCallback 作为一个参数传递给 ProxyFactory.getProxy() 方法,这样消费者可以在调用时自定义容错逻辑。

java 复制代码
public class ProxyFactory {

    public static <T> T getProxy(Class interfaceClass,ErrorCallback errorCallback) {
        // 用户配置

        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),
                        method.getParameterTypes(), args);

                HttpClient httpClient = new HttpClient();

                // 服务发现
                List<URL> list = MapRemoteRegister.get(interfaceClass.getName());

                URL url = Loadbalance.random(list);
                String result=null;
                // 服务调用
                try {

                    result = httpClient.send(url.getHostname(), url.getPort(), invocation);
                }
                catch (Exception e)
                {

                    // 如果传入了 ErrorCallback,则调用
                    result = handleFailure(e, invocation, url, errorCallback);
                }


                return result;

            }
            // 服务容错逻辑的处理方法
            private String handleFailure(Exception e, Invocation invocation, URL url, ErrorCallback errorCallback) {
                String result = null;
                int retryCount = 3;  // 定义重试次数
                long retryDelay = 2000; // 定义重试间隔(毫秒)

                // 重试策略:尝试多次重试
                for (int i = 0; i < retryCount; i++) {
                    try {
                        Thread.sleep(retryDelay); // 等待一段时间后重试
                        HttpClient httpClient = new HttpClient();
                        result = httpClient.send(url.getHostname(), url.getPort(), invocation);
                        if (result != null) {
                            return result;  // 成功返回
                        }
                    } catch (Exception retryException) {
                        // 继续重试
                        if (i == retryCount - 1) {
                            // 重试失败,执行回调策略
                            result = fallback(invocation, retryException, errorCallback);
                        }
                    }
                }
                return result;
            }

            // 调用回调策略
            private String fallback(Invocation invocation, Exception e, ErrorCallback errorCallback) {
                if (errorCallback != null) {
                    return errorCallback.handleError(invocation, e);  // 调用消费者传递的回调
                }
                // 如果没有回调,返回一个默认的错误信息
                return "服务不可用,请稍后再试";
            }

        });



        return (T) proxyInstance;
    }

}

RPC包下ErrorCallback接口:

java 复制代码
public interface ErrorCallback {
    String handleError(Invocation invocation, Exception e);
}

Comsumer类:消费者传递 ErrorCallback : 在 Consumer 类中,我们可以传递一个自定义的 ErrorCallback 实现。

java 复制代码
public class Comsumer {
    public static void main(String[] args) {
        // 创建一个自定义的 ErrorCallback 实现
        ErrorCallback customErrorCallback = new ErrorCallback() {
            @Override
            public String handleError(Invocation invocation, Exception e) {
                // 这里可以定义自己的容错逻辑
                System.out.println("自定义错误处理:服务调用失败,错误信息:" + e.getMessage());
                // 返回自定义的错误信息或执行其他操作
                return "自定义错误响应: 服务不可用";
            }
        };
        HelloService helloService = ProxyFactory.getProxy(HelloService.class,customErrorCallback);
        String result = helloService.sayHello("zhouyu123123123");
        System.out.println(result);
    }
}

7、服务mock,服务没有开发完成先返回一个mock结果。然后通过配置文件设置返回值。

java 复制代码
public class ProxyFactory {

    public static <T> T getProxy(Class interfaceClass,ErrorCallback errorCallback) {
        // 用户配置

        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String mock = System.getProperty("mock");
                if (mock!=null && mock.startsWith("retuen:"))
                {
                    String result = mock.replace("return:", "");
                    return result;
                }
            .................
相关推荐
寻找优秀的自己24 分钟前
WebSocket 实现指南
websocket·网络协议·go
自由自在的小Bird30 分钟前
计算机网路HTTP、TCP详解
网络·网络协议·http
komo莫莫da32 分钟前
第6章——HTTP首部
网络·网络协议·http
大丈夫立于天地间1 小时前
OSPF - LSA对照表
网络·网络协议·学习·算法·信息与通信
Be_insighted2 小时前
OKHttp调用第三方接口,响应转string报错okhttp3.internal.http.RealResponseBody@4a3d0218
网络协议·http·okhttp
IT 古月方源2 小时前
华为设备的监控和管理
运维·服务器·网络·安全·网络安全·华为
Eagle104fred3 小时前
pymodubs TCP 无链接报错: pymodbus.exceptions.ConnectionException: Modbus Error
网络·网络协议·tcp/ip
华为云开发者联盟3 小时前
解读ENS网络连接,面向多云多池网络的高效互联
网络·数据中心·ens·华为云stack·vpc网络
leisigoyle3 小时前
第四届智能系统、通信与计算机网络国际学术会议(ISCCN 2025)
网络·人工智能·计算机网络
zhangxueyi3 小时前
一次完成Win10下MySQL 9.1 的安装
网络·数据库·sql·mysql·oracle