粗漢手撕RPC核心原理-Netty版

粗漢手撕RPC核心原理-Netty版


源碼地址: https://gitee.com/kylin1991_admin/mini-tomcat/tree/master/nio-tomcat

環境準備

1、maven

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
      <!-- 5 版本已經被丟棄了-->
      <dependency>
          <groupId>io.netty</groupId>
          <artifactId>netty-all</artifactId>
          <version>4.1.48.Final</version>
      </dependency>

2、插件:Lombok

1、protocol協議包

  • 直接用一個協議類就好了,用於數據傳輸過程的包裝類
@Data
public class InvokeProtocol implements Serializable {
    private String className;
    private String methodName;
    private Class<?>[] params;
    private Object[] values;
}

2、registry註冊中心

a、RpcRegistry註冊中心類
  • 主要就是服務開啓。用netty來開啓服務
public class RpcRegistry {
    private static final int PORT = 8080;

    public static void main(String[] args) {
        new RpcRegistry().start();
    }

    private void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap registry = new ServerBootstrap()
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel serverSocketChannel) throws Exception {
                        ChannelPipeline pipeline = serverSocketChannel.pipeline();
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                        pipeline.addLast(new LengthFieldPrepender(4));

                        pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                        pipeline.addLast("encoder", new ObjectEncoder());

                        pipeline.addLast(new RegistryHandler());
                    }
                });

        try {
            ChannelFuture f = registry.bind(this.PORT).sync();
            System.out.println("start rpc registry success , prot : " + this.PORT);
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}
b、RegistryHandler初始化邏輯類
  • 邏輯處理的,主要是初始化容器,然後用於回調。其實這裏也可以用Proxy來遠程調用。
  • 所以真正的情況就是consumer遠程調用。provider遠程調用。簡化簡化哈哈
public class RegistryHandler extends ChannelInboundHandlerAdapter {
    private static final List<String> classNames = new ArrayList<>();
    private static final Map<String, Object> serverMap = new HashMap<>();
    // 指定掃描的實現路徑,正常的情況是去讀取配置文件,爲了方便
    private static final String PACKAGE_NAME = "org.example.provider";

    public RegistryHandler() {
        scannerClassName(PACKAGE_NAME);
        doRegistry();
    }

    private void doRegistry() {
        if (classNames == null) return;
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                Class<?> server = clazz.getInterfaces()[0];
                serverMap.put(server.getName(), clazz.newInstance());

            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void scannerClassName(String packageName) {
        URL url = this.getClass().getResource("/" + packageName.replaceAll("\\.", "/"));
        File files = new File(url.getFile());
        for (File file : files.listFiles()) {
            if (file.isDirectory()) {
                scannerClassName(file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replace(".class", ""));
            }
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Object result = null;
        if (msg instanceof InvokeProtocol) {
            InvokeProtocol request = (InvokeProtocol) msg;
            if (serverMap.containsKey(request.getClassName())) {
                Object service = serverMap.get(request.getClassName());
                Method method = service.getClass().getMethod(request.getMethodName(), request.getParams());
                result = method.invoke(service, request.getValues());
            }
        }
        ctx.writeAndFlush(result);
        ctx.close();

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("registry exception ");
        throw new RuntimeException(cause.getMessage());
    }
}

3、api

  • 用於提供接口使用的jar包
public interface IHelloService {
    String sayHello(String content);
}

4、provider服務提供方

public class HelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String content) {
        System.out.println("request in :" + content);
        return "SUCCESS";
    }
}

5、Consumer客戶調用方

還是要用AOP代理的思想

a、Consumer調用類
public class Consumer {
    public static void main(String[] args) {
        IHelloService helloService = ProxyClient.create(IHelloService.class);
        System.out.println(helloService.sayHello("七樓"));
    }
}
b、RpcProxyClient代理類
  • 利用的jdk的動態代理,然後代理時採用netty來做客戶端請求
public class RpcProxyClient {
    public static <T> T create(Class<?> interfacesCls) {
        return (T) Proxy.newProxyInstance(interfacesCls.getClassLoader(), new Class<?>[]{interfacesCls}, new ProxyHandler());
    }

    private static class ProxyHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            InvokeProtocol request = new InvokeProtocol();
            request.setClassName(method.getDeclaringClass().getName());
            request.setMethodName(method.getName());
            request.setParams(method.getParameterTypes());
            request.setValues(args);

            RemoteHandler remoteHandler = new RemoteHandler();
            EventLoopGroup workerGroup = new NioEventLoopGroup();

            Bootstrap client = new Bootstrap()
                    .group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();

                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));

                            pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast("encoder", new ObjectEncoder());

                            pipeline.addLast(remoteHandler);

                        }
                    });


            ChannelFuture f = client.connect("localhost", 8080).sync();
            f.channel().writeAndFlush(request).sync();
            f.channel().closeFuture().sync();
            workerGroup.shutdownGracefully();
            return remoteHandler.getResult();
        }
    }
}
c、RemoteHandler遠程調用邏輯類
  • 接受返回結果
public class RemoteHandler extends ChannelInboundHandlerAdapter {
    private Object result;


    public Object getResult() {
        return result;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        result = msg;
        super.channelRead(ctx, msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("rpc registry exception");
        throw new RuntimeException(cause.getMessage());
    }
}

6、測試服務類

  • 啓動RpcRegistry

  • 直接consumer上操作就好了

好了。至此。直接啓動Netty版的RPC就完成了就可以訪問了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章