基於java的Grpc實例創建及基於Nginx的Grpc服務端負載均衡


  Grpc是googe開發的,是一款語言中立、平台中立、開源的遠程過程調用(RPC)系統。新公司的項目服務之間的調用使用的Grpc來實現服務間的調用,這邊一開始接到的工作內容是基於Nginx實現Grpc服務端的負載均衡。Nginx的1.13及以上版本是支持grpc的反向代理和負載均衡的。但是公司的nginx服務器的版本是1.10的,所以沒辦法直接使用grpc的代理。只能使用更底層的tcp層的負載均衡。最終服務跑起來是感覺挺簡單的,但是nginx的基礎太差,所以過程有點曲折。還是記錄下吧。

  文章分兩部分,一個是創建簡單的Grpc客戶端和服務端的例子(其實也是用的網上的demo,這邊就貼一下源碼,講下更細的實現步驟),然后對比下Nginx的Grpc負載均衡和Tcp的負載均衡。

  一、Java創建Grpc客戶端和服務端的例子(創建的配置信息相關的代碼基本網上博客的,忘記是哪篇文章了,所以暫時沒法給出轉載鏈接。)

  1、在開發工具ide上創建一個maven project。打包方式選擇jar。

  2、在POM.xml上增加grpc相關的依賴及maven的打包插件

<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>1.17.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.17.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.17.1</version>
    </dependency>
</dependencies>
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.4.1.Final</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.0</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.0.0:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.0.0:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
    </plugins>
</build>

  3、在項目下的路徑src/main下面創建proto文件夾,並在里面創建一個hello.proto文件。具體如下截圖。

  

  4、在hello.proto文件上輸入,相應的配置信息,用來映射生成java代碼。里面的內容就是生成一個MyRPC的服務提供一個sayHi的接口,接口需要傳遞一個request類的實例,該request實例只有一個name的字段。然后進行相應的業務代碼處理之后,返回一個response類的實例,也是只有一個name的字段。

    如果進行到這邊,看到第2步添加依賴上面的 <execution>標簽可能報錯,先暫時不要管他。直接進行第5步。

syntax = "proto3";
option java_package = "com.qidai.proto";
option java_outer_classname = "MyThing";

message Request {
    string name = 1;
}
message Response {
    string name = 2;
}
service MyRPC {
    rpc sayHi(Request) returns(Response);
}

  5、運行項目,右擊項目Run as -->maven build....->protobuf:compile以及protobuf:compile-custom,這樣就編譯生成了相應的代碼了。不過存放的路徑不對,需要自己拷貝到相應的項目目錄下。

  6、grpc的客戶端和服務端代碼需要自己編寫。不過這一塊的demo已經很全了。c+v然后改成自己的自己需要的就行了。

  服務端demo:

package server;
import com.qidai.proto.MyRPCGrpc;
import com.qidai.proto.MyThing;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import service.RequestImpl;

import java.io.IOException;
public class Server {
    private static final int PORT = 2222;
    private final io.grpc.Server server;
    public Server() throws IOException {
        //這個部分啟動server
        this.server = ServerBuilder.forPort(PORT)
                .addService(new RequestImpl())
                .build()
                .start();
        System.out.println("Server1 Started ...");
    }
    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        Server server = new Server();
        //block Server防止關閉
        server.blockUntilShutdown();
    }
    
}

  客戶端demo

package client;
import com.qidai.proto.MyRPCGrpc;
import com.qidai.proto.MyRPCGrpc.MyRPCBlockingStub;
import com.qidai.proto.MyThing;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.TimeUnit;
public class Client {
    private final ManagedChannelBuilder<?> managedChannelBuilder;
    private final MyRPCBlockingStub blockingStub;
    private final ManagedChannel channel;
    public Client(String name, int port) {
        managedChannelBuilder = ManagedChannelBuilder.forAddress(name, port);
        channel = managedChannelBuilder.usePlaintext().build();
        blockingStub = MyRPCGrpc.newBlockingStub(channel);
    }
    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }
    public  void sayHi(String name){
        MyThing.Request request = MyThing.Request.newBuilder().setName(name).build();
        MyThing.Response response = blockingStub.sayHi(request);
        System.out.println(response.getName());
    }
    public static void main(String[] args) throws Exception{
        Client client = new Client("localhost", 5005);
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            //進行rpc調用的真正邏輯
            client.sayHi("Hello Server1111 ->5005 " + i);
        }
        client.shutdown();
        Client client2 = new Client("localhost", 5005);
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            //進行rpc調用的真正邏輯
            client2.sayHi("Hello Server2222 ->5005 " + i);
        }
        client2.shutdown();
    }
}

  7、接下來就是才是比較關鍵的一步,實現自己的grpc服務端的業務代碼。主要的關鍵步驟就是繼承grpc自動映射出來的抽象類。是不是很熟悉,沒錯就是proto文件里面配置的服務。然后重寫服務里面配置的方法即可。最后放心大膽的去根據傳遞的request參數去做相關的業務邏輯的處理。並用response封裝需要返回的接口。(此處的request與response均是grcp根據proto配置文件映射出來的相關實體類。)

package service;


import com.qidai.proto.MyRPCGrpc.MyRPCImplBase;
import com.qidai.proto.MyThing.Response;

public class RequestImpl extends MyRPCImplBase {
    
    @Override
    public void sayHi(com.qidai.proto.MyThing.Request request,
            io.grpc.stub.StreamObserver<com.qidai.proto.MyThing.Response> responseObserver) {
        //proto文件上定義的response返回信息
        Response response;
        
        System.out.println("Request>>>say::" + request.getName());
        //AccountQryResponse response = QryAccountProto.AccountQryResponse.newBuilder().setRc(1).setAmount(666).build();
        response = Response.newBuilder().setName("Response11111>>>say:::hello_client"+request.getName()).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
        
        }
    
}

  二、Grpc服務基於nginx(1.12.2)實現負載均衡。下面直接貼nginx相關的配置,服務端和客戶端的代碼改動都很小。只需調整ip和port的值即可。其他的不需要改動。

  TCP層負載均衡配置

stream {

   log_format proxy '$remote_addr [$time_local] '
                 '$protocol $status $bytes_sent $bytes_received '
                 '$session_time "$upstream_addr" '
                 '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
   include ./conf.d/*.tcpstream;

   upstream grpc {
        server 127.0.0.1:2223;
        server 127.0.0.1:2222;
    }

    server {
    
    error_log       logs/device5001_error.log;
    access_log      logs/device5001_access.log proxy;

        listen 5005;
        proxy_pass grpc;
    }
   
}

  grpc的負載均衡配置(grpc的支持在nginx1.13之后才有,所以這里是1.17.0)

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
 
    keepalive_timeout  65;

    gzip  on;

    upstream grpcservers {
    server 127.0.0.1:2222;
    server 127.0.0.1:2223;
    }

    server {
        listen       8080 http2;
        server_name  localhost;
             
        location / {
            grpc_pass grpc://grpcservers;
        }
     
    }


}

   最后分別啟動nginx1.12.2和nginx1.17.0,並在ide上啟動服務端和客戶端,更改相應的客戶端端口。就可以看到控制台打印不同的信息了。tcp和grcp的負載均衡的效果是不一樣的。這也是我客戶端new 了一個client,然后又new 了一個client2的原因。比較懶,效果圖就不貼了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM