在上一篇文章《java使用protobuf-maven-plugin的插件编译proto文件》中,我们使用protobuf-maven-plugin已经生成了grpc的调用的库,这篇文章我们将讲解使用SpringCloud及nacos搭建grpc服务。
具体的实现步骤:
1. 先在github下载一个nacos的release版本应用,下载地址。 解压缩之后,进到nacos目录,执行如下指令启动nacos,这里我们测试,所以启动的是单机服务:
sh ./bin/startup.sh -m standalone
运行成功后在网页访问:http://localhost:8848/nacos,打开后进到nacos登录页面,使用默认账号/密码:nacos/nacos进行登录。
登录成功后,我们可以看到nacos提供了ConfigManagement和ServiceManagement,我们要看的是服务注册中心的ServiceManagement。
2. 我们在上一章lib的工程中,创建三个module,分别为cloud-grpc-provider9090和cloud-grpc-provider9091和cloud-grpc-consumer10020。 前两个提供服务的grpc的provider的服务端, 后面一个consumer为grpc的消费端。项目结构如下:
3. 在父工程的POM文件中添加如下包依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<jackson.version>2.11.1</jackson.version>
<protobuf.plugin.version>0.6.1</protobuf.plugin.version>
<spring.boot.version>2.3.9.RELEASE</spring.boot.version>
<springcloud.version>Hoxton.SR9</springcloud.version>
<springcloud.alibaba.version>2.1.0.RELEASE</springcloud.alibaba.version>
<protobuf.version>3.14.0</protobuf.version>
<grpc.java.version>1.35.0</grpc.java.version>
<!--grpc.starter.version>2.11.0.RELEASE</grpc.starter.version-->
<grpc.starter.version>2.13.1.RELEASE</grpc.starter.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- jackson-xml -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- grpc -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>${grpc.starter.version}</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-autoconfigure</artifactId>
<version>${grpc.starter.version}</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>${grpc.starter.version}</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-autoconfigure</artifactId>
<version>${grpc.starter.version}</version>
</dependency>
<!-- proto -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-context</artifactId>
<version>${grpc.java.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
<version>${grpc.java.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
这里我们使用的spring-boot版本与grpc-client-spring-boot-starter和grpc-server-spring-boot-starter使用的版本不同,所以后面我们需要在grpc的starter排除掉默认的spring-boot版本。同时grpc的相关一些库都使用统一的版本。
这里可以看到grpc底层使用的是netty实现的消息通讯
4. 配置application.yml文件
//provider的application.yml
server:
port: 8081
grpc:
server:
port: 9091
security:
enabled: false
spring:
application:
name: cloud-grpc-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# actuator management
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'
//consumer的application.yml
server:
port: 10020
spring:
application:
name: cloud-grpc-comsumber
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
grpc:
client:
cloud-grpc-provider:
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: PLAINTEXT
GLOBAL:
security:
client-auth-enabled: false
以上是provider和consumer的application.yml配置文件。 这里我们使用的同一台机器进行测试,所以要注意不同的provider和consumer应用的server的端口和grpc的端口不要相同。
同时在provider和consumer,将grpc的security的enable开关关掉。consumer端要注意的是设置negotiationType的类型为PLAINTEXT
5. provider和consumer引入相应的包
provider的maven引入:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.ckeen</groupId>
<artifactId>cloud-grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
consumer的maven引入:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.ckeen</groupId>
<artifactId>cloud-grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
两个包都引入了lib包,作为客户端和服务端通讯协议。使用nacos作为注册中心,实现负载均衡。
6. provider的service编写
@GrpcService
public class GreeterService extends SimpleGrpc.SimpleImplBase {
private static Logger log = LoggerFactory.getLogger(GreeterService.class);
// 引入该参数是为了做负载均衡验证
@Value("${grpc.server.port}")
private String serverPort;
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String message = "server port:" + serverPort + ",Hello " + request.getName();
final HelloReply.Builder replyBuilder = HelloReply.newBuilder().setMessage(message);
responseObserver.onNext(replyBuilder.build());
responseObserver.onCompleted();
log.info("Returning " +message);
responseObserver.onError(Status.INVALID_ARGUMENT.withDescription("测试出错").asException());
}
}
首先使用GrpcService注解来标识服务,该服务继承自proto文件生成的grpc的SimpleGrpc.SimpleImplBase,同时实现了sayHello的方法,这里我们通过responseObserver将结果返回。
7. consumer的消费
consumer端比较简单,直接使用@GrpcClient的注解生成一个SimpleGrpc的Stub,然后进行调用即可
@Service
public class GreeterService {
@GrpcClient("cloud-grpc-provider")
private SimpleGrpc.SimpleBlockingStub simpleBlockingStub;
public String greet(String name) {
try {
HelloReply response = simpleBlockingStub.sayHello(HelloRequest.newBuilder().setName(name).build());
return response.getMessage();
} catch (final StatusRuntimeException e) {
return "FAILED with " + e.getStatus().getCode();
}
}
}
最终我们可以写一个controller来调用该服务进行测试,实现代码如下:
@Resource
private GreeterService greeterService;
@GetMapping("greeter")
public String greet(@RequestParam("name")String name){
String result = greeterService.greet(name);
return result;
}
8. 测试验证
先启动两个provider,我们可以在nacos的service list看到两个provider的注册信息:
启动consumer访问http://localhost:10020/greeter?name=keen可以看到有返回,刷新后端口还有变化
到此我们简单的一个grpc的服务就已经实现了