基于 Netty 的 RPC 调用

RPC 全称 Remote Procedure Call(远程过程调用),本质上是在两个不同机器之间通过网络传输数据,但是在本地看来是通过接口调用一样。

引入依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.45.Final</version>
</dependency>
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>

编写公共类

定义 Class 信息类

@Data
public class ClassInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    private Class<?> klass;
    private String classname;
    private ClassLoader classLoader;
    private Class<?>[] interfaces;
}

定义远程传输的对象类

@Data
public class RpcInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    private String classname;
    private String methodName;
    private Object[] args;
    private Class<?>[] argsTypes;

    public RpcInfo(String classname, String methodName, Object[] args, Class<?>[] argsTypes) {
        this.classname = classname;
        this.methodName = methodName;
        this.args = args;
        this.argsTypes = argsTypes;
    }
}

编写服务端

首先定义需要暴露接口和的实现类。这两个类在包jonkee.netty.rpc.server.rpcsevice里面。

public interface UserService {
    Map<String, Object> findById(String userId);
}

public class UserServiceImpl implements UserService {
    @Override
    public Map<String, Object> findById(String userId) {
        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("id", userId);
        hashMap.put("name", "鲁班" + userId + "号");
        return hashMap;
    }
}

然后定义 ChannelHandler。

public class RpcServerHandler extends SimpleChannelInboundHandler<Object> {
    //需要暴露的接口的包名
    private static final String BASE_SERVICE_PATH = "jonkee.netty.rpc.server.rpcsevice";

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {
        System.out.println("收到请求");
        RpcInfo rpcInfo = (RpcInfo) msg;
        Reflections reflections = new Reflections(BASE_SERVICE_PATH);
        Class clazz = Class.forName(BASE_SERVICE_PATH + rpcInfo.getClassname().substring(rpcInfo.getClassname().lastIndexOf(".")));
        // 通过反射框架获取客户端需要调用的实现类
        Set<Class<?>> classSet = reflections.getSubTypesOf(clazz);
        if (classSet.isEmpty()) {
            throw new Exception("不合法的服务调用");
        }
        if (classSet.size() > 1) {
            throw new Exception("实现类不止一个");
        }
        // 通过反射获取本地实例
        Class impl = (Class) classSet.toArray()[0];
        Method method = impl.getMethod(rpcInfo.getMethodName(), rpcInfo.getArgsTypes());
        // 反射获取结果
        Object invoke = method.invoke(impl.newInstance(), rpcInfo.getArgs());
        channelHandlerContext.channel().writeAndFlush(invoke);
        System.out.println("返回数据成功");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连上了");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

编写服务端启动类。

public class RpcServer {
    public static void main(String[] args) throws InterruptedException {
        new RpcServer().start();
    }

    private void start() throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup worker = new NioEventLoopGroup();
        serverBootstrap.group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                        pipeline.addLast(new ObjectEncoder());
                        pipeline.addLast(new RpcServerHandler());
                    }
                });
        ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
        System.out.println("Rpc server started successfully");
        channelFuture.channel().closeFuture().sync();
    }
}

编写客户端

定义一个本地接口,跟服务端的一样。

public interface UserService {
    Map<String, Object> findById(String userId);
}

编写代理类,先定义一个 ChannelHandler。

public class ClientChannelHandler extends ChannelInboundHandlerAdapter {
    private Object result;

    public Object getResult() {
        return result;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.result = msg;
        // 关闭上下文
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

编写代理类。

public class ClientProxy {
    public static final Map<String, ClassInfo> CLASS_CACHE = new ConcurrentHashMap<>();

    public static Object proxy(Class<?> clazz) {
        // 从缓存中获取类信息
        ClassInfo classInfo = getClassInfoFromCache(clazz);
        final ClassInfo info = classInfo;
        return Proxy.newProxyInstance(classInfo.getClassLoader(), classInfo.getInterfaces(), (proxy, method, args) -> {
            // 封装远程过程调用需要的信息
            RpcInfo rpcInfo = new RpcInfo(info.getClassname(), method.getName(), args, method.getParameterTypes());
            ClientChannelHandler clientChannelHandler = new ClientChannelHandler();
            Bootstrap bootstrap = new Bootstrap();
            EventLoopGroup group = new NioEventLoopGroup(1);
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast(new ObjectEncoder());
                            pipeline.addLast(clientChannelHandler);
                        }
                    });
            // 连接服务器
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888)).sync();
            Channel channel = channelFuture.channel();
            channel.writeAndFlush(rpcInfo).sync();
            System.out.println("正在获取数据");
            channel.closeFuture().sync();
            System.out.println("获取到了数据");
            return clientChannelHandler.getResult();
        });
    }

    private static ClassInfo getClassInfoFromCache(Class<?> clazz) {
        ClassInfo classInfo = CLASS_CACHE.get(clazz.getName());
        if (classInfo == null) {
            classInfo = new ClassInfo();
            classInfo.setKlass(clazz);
            classInfo.setClassLoader(clazz.getClassLoader());
            if (clazz.isInterface()) {
                classInfo.setInterfaces(new Class[]{clazz});
            } else {
                classInfo.setInterfaces(clazz.getInterfaces());
            }
            classInfo.setClassname(clazz.getName());
            CLASS_CACHE.put(clazz.getName(), classInfo);
        }
        return classInfo;
    }
}

编写调用者。

public class ClientMain {
    public static void main(String[] args) {
        UserService userService = (UserService) ClientProxy.proxy(UserService.class);
        for (int i = 1; i < 5; i++) {
            Map<String, Object> userInfo = userService.findById(String.valueOf(i));
            System.out.println(userInfo);
        }
    }
}

使用

启动服务器

Rpc server started successfully

启动客户端

正在获取数据
获取到了数据
{name=鲁班1号, id=1}
正在获取数据
获取到了数据
{name=鲁班2号, id=2}
正在获取数据
获取到了数据
{name=鲁班3号, id=3}
正在获取数据
获取到了数据
{name=鲁班4号, id=4}

可以看到,客户端通过代理将请求信息发送到服务端,服务端返回之后客户端获取到了数据。

使用Netty模拟RPC调用需要先了解RPC的基本概念和原理,以及Netty框架的使用方法。 RPC(Remote Procedure Call)远程过程调用是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用显式地编写远程调用的代码。RPC通常基于客户端/服务器模型,客户端向服务器发送RPC请求,服务器响应请求并返回结果。 Netty是一个高性能的、异步的、事件驱动的网络编程框架,它可以轻松地实现RPC调用。 下面是一个简单的Java代码示例,演示如何使用Netty模拟RPC调用: 1. 首先需要定义一个接口,这个接口定义了要远程调用的方法: ```java public interface HelloService { String sayHello(String name); } ``` 2. 接下来创建一个实现类,实现HelloService接口: ```java public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "Hello, " + name + "!"; } } ``` 3. 创建一个服务端程序,启动Netty服务端,并将HelloServiceImpl注册到服务端: ```java public class Server { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ServerHandler()); } }); ChannelFuture f = b.bind(8888).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } private static class ServerHandler extends SimpleChannelInboundHandler<Object> { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof RpcRequest) { RpcRequest request = (RpcRequest) msg; String className = request.getClassName(); String methodName = request.getMethodName(); Class<?>[] parameterTypes = request.getParameterTypes(); Object[] parameters = request.getParameters(); // 根据类名获取实现类 Class<?> clazz = Class.forName(className); Object service = clazz.newInstance(); // 根据方法名和参数类型获取方法 Method method = clazz.getMethod(methodName, parameterTypes); // 执行方法 Object result = method.invoke(service, parameters); // 返回结果 ctx.writeAndFlush(result); } } } } ``` 4. 创建一个客户端程序,通过Netty客户端向服务端发送RPC请求: ```java public class Client { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); pipeline.addLast(new ClientHandler()); } }); ChannelFuture f = b.connect("localhost", 8888).sync(); // 发送RPC请求 RpcRequest request = new RpcRequest(); request.setClassName("com.example.HelloServiceImpl"); request.setMethodName("sayHello"); request.setParameterTypes(new Class<?>[] { String.class }); request.setParameters(new Object[] { "world" }); f.channel().writeAndFlush(request); // 等待响应 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } private static class ClientHandler extends SimpleChannelInboundHandler<Object> { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理响应结果 System.out.println(msg); ctx.channel().close(); } } } ``` 这样,我们就通过Netty模拟了一次RPC调用。当客户端向服务端发送RPC请求时,服务端会根据请求参数调用相应的方法并返回结果,客户端收到响应结果后输出到控制台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值