《RPC实战与核心原理》学习笔记Day5

06 | RPC实战:剖析gRPC源码,动手实现一个完整的RPC

我们通过动态代理技术,屏蔽RPC调用的细节,从而让使用者能够面向接口编程。

什么是gRPC?

gRPC是由Google开发并且开源的一款高性能、跨语言的RPC框架,当前支持C、Java和Go语言,当前Java版本最新Release版是1.51.3。

什么是protobuf?

protocol buffers是一种语言无关、平台无关、可扩展的序列化结构数据方法,可用于通信协议、数据存储等。
我们可以定义数据结构,然后使用特殊生成的源代码在各种数据流中使用各种语言进行编写和读取数据结构,也可以更新数据结构。

protobuf的三大特点:

  1. 语言无关,平台无关
  2. 灵活、高效
  3. 扩展性、兼容性好

下面我们来看一下如何使用gRPC。

首先我们需要安装protobuf,如果你使用Mac电脑,那么可以运行下面的命令来安装protobuf。

brew install protobuf

执行成功后,可以运行下面的命令来查看相关版本信息。

protoc --version

libprotoc 3.21.9

然后我们来创建下面的proto文件hello.proto。

syntax = "proto3";

option java_generic_services = true;
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

我们可以使用下面的命令来自动生成Java代码文件。

protoc ./hello.proto --java_out=./

命令执行完成后,会在当前目录下,生成代码文件,对应的目录结构如下。

tree .
.
├── hello.proto
└── io
    └── grpc
        └── examples
            └── helloworld
                ├── Greeter.java
                ├── Hello.java
                ├── HelloReply.java
                ├── HelloReplyOrBuilder.java
                ├── HelloRequest.java
                └── HelloRequestOrBuilder.java

上面就完成了proto文件转换的过程。

接下来,我们看怎么在Java工程中完整的使用gRPC。

我们创建一个空的Maven工程,在pom.xml中引用必要的依赖以及protobuf Maven插件, 完整的pom.xml内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>sample.grpc</groupId>
	<artifactId>grpc-sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<grpc.version>1.52.1</grpc.version>
		<protobuf.version>3.21.9</protobuf.version>
	</properties>


	<dependencies>
		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-netty-shaded</artifactId>
			<version>1.29.0</version>
		</dependency>
		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-protobuf</artifactId>
			<version>1.29.0</version>
		</dependency>
		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-stub</artifactId>
			<version>1.29.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>annotations-api</artifactId>
			<version>6.0.53</version>
			<scope>provided</scope>
		</dependency>

	</dependencies>

	<build>
		<extensions>
			<extension>
				<groupId>kr.motd.maven</groupId>
				<artifactId>os-maven-plugin</artifactId>
				<version>1.6.2</version>
			</extension>
		</extensions>
		<plugins>
			<plugin>
				<groupId>org.xolstice.maven.plugins</groupId>
				<artifactId>protobuf-maven-plugin</artifactId>
				<version>0.6.1</version>
				<configuration>
					<protocArtifact>com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier}</protocArtifact>
					<pluginId>grpc-java</pluginId>
					<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.29.0:exe:${os.detected.classifier}</pluginArtifact>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>compile</goal>
							<goal>compile-custom</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

然后将上面的hello.proto文件复制到src/main/proto目录下面创建hello.proto目录下, 这个目录是固定的。

pom.xml文件准备好以后,我们来运行下面的工程编译命令。

maven compile

编译结束后,会在target/generated-sources目录下维护自动生成的代码,目录结构如下。

tree target/generated-sources
target/generated-sources
└── protobuf
    ├── grpc-java
    │   └── io
    │       └── grpc
    │           └── examples
    │               └── helloworld
    │                   └── GreeterGrpc.java
    └── java
        └── io
            └── grpc
                └── examples
                    └── helloworld
                        ├── Greeter.java
                        ├── Hello.java
                        ├── HelloReply.java
                        ├── HelloReplyOrBuilder.java
                        ├── HelloRequest.java
                        └── HelloRequestOrBuilder.java

11 directories, 7 files

接下来,我们来编写服务器端,代码如下。

package io.grpc.examples.helloworld;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

public class HelloWorldServer {


    private Server server;

    private void start() throws IOException {
        /* The port on which the server should run */
        int port = 50051;
        server = ServerBuilder.forPort(port)
                .addService(new GreeterImpl())
                .build()
                .start();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                try {
                    HelloWorldServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    /**
     * Await termination on the main thread since the grpc library uses daemon threads.
     */
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    /**
     * Main launches the server from the command line.
     */
    public static void main(String[] args) throws IOException, InterruptedException {
        final HelloWorldServer server = new HelloWorldServer();
        server.start();
        server.blockUntilShutdown();
    }

    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

        @Override
        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            System.out.println("=====server=====");
            System.out.println("server: Hello " + req.getName());
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

编写完成后,直接运行,这样会启动一个Server,端口是50051。

最后,我们来编写客户端,代码如下。

package io.grpc.examples.helloworld;

import java.util.concurrent.TimeUnit;

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

public class HelloWorldClient {

    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    /** Construct client for accessing HelloWorld server using the existing channel. */
    public HelloWorldClient(Channel channel) {
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    /** Say hello to server. */
    public void greet(String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response;
        try {
            response = blockingStub.sayHello(request);
        } catch (StatusRuntimeException e) {
            return;
        }
        System.out.println("Greeting: " + response.getMessage());
    }

    public static void main(String[] args) throws Exception {
        String user = "hahahahaha";
        // Access a service running on the local machine on port 50051
        String target = "localhost:50051";
        // Allow passing in the user and target strings as command line arguments
        if (args.length > 0) {
            if ("--help".equals(args[0])) {
                System.err.println("Usage: [name [target]]");
                System.err.println("");
                System.err.println("  name    The name you wish to be greeted by. Defaults to " + user);
                System.err.println("  target  The server to connect to. Defaults to " + target);
                System.exit(1);
            }
            user = args[0];
        }
        if (args.length > 1) {
            target = args[1];
        }

        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                .usePlaintext()
                .build();
        try {
            HelloWorldClient client = new HelloWorldClient(channel);
            client.greet(user);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

当我们多次运行客户端程序后,我们可以在服务器端的控制台上,看到如下输出。

=====server=====
server: Hello hahahahah
=====server=====
server: Hello hahahahah
=====server=====
server: Hello hahahahah

这说明,客户端和服务器端在gRPC框架下面通信正常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一个基于NIO的高性能网络通信框架,它的核心原理是通过事件驱动和异步处理机制来实现高效的网络传输。其特点是可扩展、可重用、易于维护、性能卓越。 RPC(Remote Procedure Call)是一种基于网络通信的分布式计算模型,通过这种模型,客户端调用服务端的远程方法就像调用本地方法一样简单。在RPC实践过程中,使用Netty框架可以有效提升RPC的性能和稳定性。 Netty的核心原理是基于事件驱动的模型,它的主要组成部分包括NioEventLoop、Channel、Buffer、Codec等。NioEventLoop是Netty中的核心组件之一,它是一个事件循环线程,通过不断遍历注册在它上面的Channel监听器,来处理网络传输过程中的事件,从而实现网络的异步非阻塞传输。 在RPC实践中,Netty通过对协议进行编解码,来实现远程方法的调用和响应过程。通常情况下,客户端与服务端之间需要使用一种协议来进行通信,需要对协议进行编解码处理,这个过程需要在客户端和服务端都实现一遍,使用Netty框架可以简化这个过程,使得开发人员只需要实现协议的编解码策略即可。 总之,Netty作为高性能网络通信框架,其核心原理是基于事件驱动的机制实现的。在RPC实践中,Netty可以高效地实现协议的编解码,提升RPC的性能和稳定性,因此在分布式计算中得到了广泛的应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值