9

java版gRPC实战之二:服务发布和调用

 3 years ago
source link: https://segmentfault.com/a/1190000040384524
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

  • 本文是《java版gRPC实战》系列的第二篇,前文《用proto生成代码》将父工程、依赖库版本、helloworld.proto对应的java代码都准备好了,今天的任务是实战gRPC服务的开发和调用,实现的效果如下图:

在这里插入图片描述

  • 本篇的具体操作如下:
  • 开发名为<font color="blue">local-server</font>的springboot应用,提供helloworld.proto中定义的gRPC服务;
  • 开发名为<font color="blue">local-client</font>的springboot应用,调用<font color="blue">local-server</font>提供的gRPP服务;
  • 验证gRPC服务能不能正常调用;
名称链接备注项目主页https://github.com/zq2599/blo...该项目在GitHub上的主页git仓库地址(https)https://github.com/zq2599/blo...该项目源码的仓库地址,https协议git仓库地址(ssh)[email protected]:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,《java版gRPC实战》系列的源码在<font color="blue">grpc-tutorials</font>文件夹下,如下图红框所示:

在这里插入图片描述

  • <font color="blue">grpc-tutorials</font>文件夹下有多个目录,本篇文章对应的代码在<font color="blue">local-server</font>和<font color="blue">local-client</font>中,如下图红框:

在这里插入图片描述

开发gRPC服务端

  • 首先要开发的是gRPC服务端,回顾前文中helloworld.proto中定义的服务和接口,如下所示,名为Simple的服务对外提供名为SayHello接口,这就是咱们接下来的任务,创建一个springboot应用,该应用以gRPC的方式提供SayHello接口给其他应用远程调用:
service Simple {
    // 接口定义
    rpc SayHello (HelloRequest) returns (HelloReply) {
    }
}
  • 基于springboot框架开发一个普通的gRPC服务端应用,一共需要五个步骤,如下图所示,接下来我们按照下图序号的顺序来开发:

在这里插入图片描述

  • 首先是在父工程<font color="blue">grpc-turtorials</font>下面新建名为<font color="">local-server</font>的模块,其build.gradle内容如下:
// 使用springboot插件
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    // 作为gRPC服务提供方,需要用到此库
    implementation 'net.devh:grpc-server-spring-boot-starter'
    // 依赖自动生成源码的工程
    implementation project(':grpc-lib')
}
  • 这是个springboot应用,配置文件内容如下:
spring:
  application:
    name: local-server
# gRPC有关的配置,这里只需要配置服务端口号
grpc:
  server:
    port: 9898
  • 新建拦截类<font color="blue">LogGrpcInterceptor.java</font>,每当gRPC请求到来后该类会先执行,这里是将方法名字在日志中打印出来,您可以对请求响应做更详细的处理:
package com.bolingcavalry.grpctutorials;

import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogGrpcInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
                                                                 ServerCallHandler<ReqT, RespT> serverCallHandler) {
        log.info(serverCall.getMethodDescriptor().getFullMethodName());
        return serverCallHandler.startCall(serverCall, metadata);
    }
}
  • 为了让LogGrpcInterceptor可以在gRPC请求到来时被执行,需要做相应的配置,如下所示,在普通的bean的配置中添加注解即可:
package com.bolingcavalry.grpctutorials;

import io.grpc.ServerInterceptor;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class GlobalInterceptorConfiguration {
    @GrpcGlobalServerInterceptor
    ServerInterceptor logServerInterceptor() {
        return new LogGrpcInterceptor();
    }
}
  • 应用启动类很简单:
package com.bolingcavalry.grpctutorials;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LocalServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LocalServerApplication.class, args);
    }
}
  • 接下来是最重要的service类,gRPC服务在此处对外暴露出去,完整代码如下,有几处要注意的地方稍后提到:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.HelloReply;
import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.Date;

@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(com.bolingcavalry.grpctutorials.lib.HelloRequest request,
                         io.grpc.stub.StreamObserver<com.bolingcavalry.grpctutorials.lib.HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName() + ", " + new Date()).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}
  • 上述GrpcServerService.java中有几处需要注意:
  • 是使用<font color="blue">@GrpcService</font>注解,再继承SimpleImplBase,这样就可以借助grpc-server-spring-boot-starter库将sayHello暴露为gRPC服务;
  • SimpleImplBase是前文中根据proto自动生成的java代码,在grpc-lib模块中;
  • sayHello方法中处理完毕业务逻辑后,调用HelloReply.onNext方法填入返回内容;
  • 调用HelloReply.onCompleted方法表示本次gRPC服务完成;
  • 至此,gRPC服务端编码就完成了,咱们接着开始客户端开发;

调用gRPC

  • 在父工程<font color="blue">grpc-turtorials</font>下面新建名为<font color="">local-client</font>的模块,其build.gradle内容如下,注意要使用spingboot插件、依赖grpc-client-spring-boot-starter库:
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'net.devh:grpc-client-spring-boot-starter'
    implementation project(':grpc-lib')
}
  • 应用配置文件grpc-tutorials/local-client/src/main/resources/application.yml,注意address的值就是gRPC服务端的信息,我这里local-server和local-client在同一台电脑上运行,请您根据自己情况来设置:
server:
  port: 8080
spring:
  application:
    name: local-grpc-client

grpc:
  client:
    # gRPC配置的名字,GrpcClient注解会用到
    local-grpc-server:
      # gRPC服务端地址
      address: 'static://127.0.0.1:9898'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

-接下来要创建下图展示的类,按序号顺序创建:

在这里插入图片描述

  • 首先是拦截类LogGrpcInterceptor,与服务端的拦截类差不多,不过实现的接口不同:
package com.bolingcavalry.grpctutorials;

import io.grpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogGrpcInterceptor implements ClientInterceptor {

    private static final Logger log = LoggerFactory.getLogger(LogGrpcInterceptor.class);

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions, Channel next) {
        log.info(method.getFullMethodName());
        return next.newCall(method, callOptions);
    }
}
  • 为了让拦截类能够正常工作,即发起gRPC请求的时候被执行,需要新增一个配置类:
package com.bolingcavalry.grpctutorials;

import io.grpc.ClientInterceptor;
import net.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Order(Ordered.LOWEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
public class GlobalClientInterceptorConfiguration {

    @GrpcGlobalClientInterceptor
    ClientInterceptor logClientInterceptor() {
        return new LogGrpcInterceptor();
    }
}
package com.bolingcavalry.grpctutorials;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LocalGrpcClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(LocalGrpcClientApplication.class, args);
    }
}
  • 接下来是最重要的服务类GrpcClientService,有几处要注意的地方稍后会提到:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.HelloReply;
import com.bolingcavalry.grpctutorials.lib.HelloRequest;
import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;
import io.grpc.StatusRuntimeException;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;

@Service
public class GrpcClientService {

    @GrpcClient("local-grpc-server")
    private SimpleGrpc.SimpleBlockingStub simpleStub;

    public String sendMessage(final String name) {
        try {
            final HelloReply response = this.simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (final StatusRuntimeException e) {
            return "FAILED with " + e.getStatus().getCode().name();
        }
    }
}
  • 上述GrpcClientService类有几处要注意的地方:
  • 用<font color="blue">@Service</font>将GrpcClientService注册为spring的普通bean实例;
  • 用<font color="blue">@GrpcClient</font>修饰SimpleBlockingStub,这样就可以通过grpc-client-spring-boot-starter库发起gRPC调用,被调用的服务端信息来自名为<font color="red">local-grpc-server</font>的配置;
  • SimpleBlockingStub来自前文中根据helloworld.proto生成的java代码;
  • SimpleBlockingStub.sayHello方法会远程调用local-server应用的gRPC服务;
  • 为了验证gRPC服务调用能否成功,再新增个web接口,接口内部会调用GrpcClientService.sendMessage,这样咱们通过浏览器就能验证gRPC服务是否调用成功了:
package com.bolingcavalry.grpctutorials;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GrpcClientController {

    @Autowired
    private GrpcClientService grpcClientService;

    @RequestMapping("/")
    public String printMessage(@RequestParam(defaultValue = "will") String name) {
        return grpcClientService.sendMessage(name);
    }
}
  • 编码完成,接下来将两个服务都启动,验证gRPC服务是否正常;

验证gRPC服务

  1. <font color="blue">local-server</font>和<font color="blue">local-client</font>都是普通的springboot应用,可以在IDEA中启动,点击下图红框位置,在弹出菜单中选择<font color="red">Run 'LocalServerApplication'</font>即可启动local-server:

在这里插入图片描述

  1. local-server启动后,控制台会提示gRPC server已启动,正在监听9898端口,如下图:

在这里插入图片描述

  1. local-client后,在浏览器输入<font color="blue">http://localhost:8080/?name=Tom</font>,可以看到响应的内容正是来自local-server的GrpcServerService.java:

在这里插入图片描述

  1. 从web端到gRPC服务端的关键节点信息如下图:

在这里插入图片描述

  • 可以看到local-server的拦截日志:

在这里插入图片描述

  • 还有local-client的拦截日志:

在这里插入图片描述

  • 至此,最简单的java版gRPC服务发布和调用验证通过,本篇的任务也就完成了,接下来的文章,咱们会继续深入学习java版gRPC的相关技术;

你不孤单,欣宸原创一路相伴

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...
https://github.com/zq2599/blog_demos


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK