第11章Nettyを使用したDubbo RPCの実装

11.1 RPCの基本的な紹介

  1. RPC(リモートプロシージャコール)—リ​​モートプロシージャコールは、コンピュータ通信プロトコルです。このプロトコルにより、プログラマーがこの対話をプログラムしなくても、あるコンピューターで実行されているプログラムが別のコンピューターのサブプログラムを呼び出すことができます。

  2. 2つ以上のアプリケーションが異なるサーバーに分散されており、それらの間の呼び出しはローカルメソッド呼び出しのようなものです
    ここに画像の説明を挿入
    。3)一般的なRPCフレームワークは、Ali's Dubbo、GoogleのgRPC、Go言語など、比較的よく知られています。 rpcx、Apache thrift、
    Springの下のSpring Cloud。
    ここに画像の説明を挿入 ここに画像の説明を挿入

11.2 RPC呼び出しのフローチャート

ここに画像の説明を挿入

11.3 PRCコールプロセスの説明

1)サービスコンシューマ(クライアント)が市内通話でサービスを呼び出す
2)クライアントスタブは、呼び出しを受信した後、メソッドやパラメータなどをネットワーク送信が可能なメッセージ本文にカプセル化します
。3)クライアントスタブは、メッセージをエンコードしてサーバーに送信します
。4)サーバースタブは、メッセージを受信して​​デコード
します。5)サーバースタブデコード結果に基づいてローカルサービスを呼び出します
6)ローカルサービスの実行と結果をサーバースタブに返します
7)サーバースタブは返されたインポート結果をエンコードしてコンシューマーに送信し
ます8)クライアントスタブはメッセージを受信して​​デコードします
9)サービス利用者(クライアント)が結果を取得します

概要:RPCの目標は、これらのステップ2〜8カプセル化することです。ユーザーはこれらの詳細を気にする必要はありません。ローカルメソッドを呼び出すようなリモートサービス呼び出しを実行できます。

11.4ダボRPCを自分で実装する(Nettyに基づく)

11.4.1要件

  1. ダボの最下層では、ネットワーク通信フレームワークとしてNettyを使用しているため、Nettyは単純なRPCフレームワークを実装する必要があります。
  2. ダボを模倣して、コンシューマーとプロバイダーはインターフェイスとプロトコルについて合意します。コンシューマーはプロバイダーのサービスをリモートで呼び出します。プロバイダーは文字列を返し、コンシューマーはプロバイダーから返されたデータを出力します。基礎となるネットワーク通信はNetty 4.1.20を使用します

11.4.2設計手順

  1. インターフェースを作成し、抽象メソッドを定義します。消費者とプロバイダー間の合意に使用されます。
  2. プロバイダーを作成します。このクラスは、コンシューマーリクエストをリッスンし、規則に従ってデータを返す必要があります。
  3. コンシューマーを作成します。このクラスは、独自の存在しないメソッドを透過的に呼び出す必要があり、内部的にNettyを使用してプロバイダーにデータを返すよう要求する必要があります
  4. 分析チャートを開発

ここに画像の説明を挿入

11.4.3コードの実装

//这个是接口,是服务提供方和 服务消费方都需要
public interface HelloService {
    String hello(String mes);
}

public class HelloServiceImpl implements HelloService{

    private static int count = 0;
    //当有消费方调用该方法时, 就返回一个结果
    @Override
    public String hello(String mes) {
        System.out.println("收到客户端消息=" + mes);
        //根据mes 返回不同的结果
        if(mes != null) {
            return "你好客户端, 我已经收到你的消息 [" + mes + "] 第" + (++count) + " 次";
        } else {
            return "你好客户端, 我已经收到你的消息 ";
        }
    }
}
Server
//ServerBootstrap 会启动一个服务提供者,就是 NettyServer
public class ServerBootstrap {
    public static void main(String[] args) {
        NettyServer.startServer("127.0.0.1", 7000);
    }
}

public class NettyServer {
    public static void startServer(String hostName, int port) {
        //不同的启动方式
        startServer0(hostName,port);
    }

    //编写一个方法,完成对NettyServer的初始化和启动

    private static void startServer0(String hostname, int port) {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                                      @Override
                                      protected void initChannel(SocketChannel ch) throws Exception {
                                          ChannelPipeline pipeline = ch.pipeline();
                                          pipeline.addLast(new StringDecoder());//编码
                                          pipeline.addLast(new StringEncoder());//解码
                                          pipeline.addLast(new NettyServerHandler()); //业务处理器

                                      }
                                  }

                    );

            ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
            System.out.println("服务提供方开始提供服务~~");
            channelFuture.channel().closeFuture().sync();

        }catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

//服务器这边handler比较简单
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //获取客户端发送的消息,并调用服务
        System.out.println("msg=" + msg);
        //客户端在调用服务器的api 时,我们需要定义一个协议
        //比如我们要求 每次发消息是都必须以某个字符串开头 "HelloService#hello#你好"
        if(msg.toString().startsWith(ClientBootstrap.providerName)) {

            String result = new HelloServiceImpl().hello(msg.toString()
            .substring(msg.toString().lastIndexOf("#") + 1));
            ctx.writeAndFlush(result);
        }
    }

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

Client
public class ClientBootstrap {
    //这里自定义协议头
    public static final String providerName = "HelloService#hello#";

    public static void main(String[] args) throws  Exception{

        //创建一个消费者
        NettyClient customer = new NettyClient();

        //创建代理对象
        HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);

        for (;; ) {
            Thread.sleep(2 * 1000);
            //通过代理对象调用服务提供者的方法(服务)
            String res = service.hello("你好 dubbo~");
            System.out.println("调用的结果 res= " + res);
        }
    }
}

public class NettyClient {

    //创建线程池
    private static ExecutorService executor = Executors.
    newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static NettyClientHandler client;
    private int count = 0;

    //编写方法使用代理模式,获取一个代理对象 与服务器交互的自定义的协议信息
    public Object getBean(final Class<?> serivceClass, final String providerName) {

        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{serivceClass}, (proxy, method, args) -> {

                    System.out.println("(proxy, method, args) 进入...." + (++count) + " 次");
                    //{}  部分的代码,客户端每调用一次 hello, 就会进入到该代码
                    if (client == null) {
                        initClient();
                    }

                    //设置要发给服务器端的信息
                    //providerName 协议头 args[0] 就是客户端调用api hello(???), 参数
                    client.setPara(providerName + args[0]);

                    return executor.submit(client).get();
                });
    }

    //初始化客户端
    private static void initClient() {
        client = new NettyClientHandler();
        //创建EventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(
                        new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast(new StringDecoder());
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(client);
                            }
                        }
                );

        try {
            bootstrap.connect("127.0.0.1", 7000).sync();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {

    private ChannelHandlerContext context;//上下文
    private String result; //返回的结果
    private String para; //客户端调用方法时,传入的参数


    //与服务器的连接创建后,就会被调用, 这个方法是第一个被调用(1)
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(" channelActive 被调用  ");
        context = ctx; //因为我们在其它方法会使用到 ctx,所以赋值传给了context
    }

    //收到服务器的数据后,调用方法 (4)
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(" channelRead 被调用  ");
        result = msg.toString();
        notify(); //唤醒等待的线程
    }

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

    //被代理对象调用, 发送数据给服务器,-> wait -> 等待被唤醒(channelRead) -> 返回结果 (3)-》5
    @Override
    public synchronized Object call() throws Exception {
        System.out.println(" call1 被调用  ");
        context.writeAndFlush(para);
        //进行wait
        wait(); //等待channelRead 方法获取到服务器的结果后,唤醒
        System.out.println(" call2 被调用  ");
        return  result; //服务方返回的结果

    }
    //(2)
    void setPara(String para) {
        System.out.println(" setPara  ");
        this.para = para;
    }
}
公開された138のオリジナル記事 いいね3 ビジター7221

おすすめ

転載: blog.csdn.net/weixin_43719015/article/details/105332165