一、背景
在小型项目中,并发不高,使用基于Restful接口即可满足需求。 在并发搞得场景,请求响应时间就决定并发量,如果还是阻塞模型,那么对线程占用还是很大。
二、RPC选型
市面上的RPC:
- Http + Feign/RestTemplate
- Dubbo2.*
- SOFA
- Motan
- GRPC
- Thrift(性能好,2倍于GRPC)
...
本文不考虑性能,可扩展性因素,仅仅入门GRPC
可能有人认为rest不算RPC,个人认为远程调用都应该算是RPC,不关心底层实现
三、使用
3.1 模块拆分
- boot-grpc-api
- boot-grpc-provider
- boot-grpc-consumer
3.2 依赖
3.2.1 boot-grpc-api
配置
依赖
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
</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.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier}</pluginArtifact>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
编写proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.teddy.rpc.user";
service UserService {
rpc findByName(FindUserByNameRequest) returns (ApiResponse) {}
}
message UserDetail {
int64 id = 1;
string name = 2;
string nick = 3;
int32 gender = 4;
int32 age = 5;
string email = 6;
string phone = 7;
}
message FindUserByNameRequest {
string name = 1;
}
message ApiResponse {
string code = 1;
string msg = 2;
bool success = 3;
UserDetail data = 4;
}
执行mvn clean complie生成代码
3.2.2 boot-grpc-provider
配置
pom.xml
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.teddy</groupId>
<artifactId>boot-grpc-api</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
启动类
@EnableDiscoveryClient
@SpringBootApplication
public class BootGrpcProvider {
public static void main(String[] args) {
SpringApplication.run(BootGrpcProvider.class, args);
}
}
RPC实现类
@Slf4j
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase{
@Override
public void findByName(FindUserByNameRequest request, StreamObserver<ApiResponse> responseObserver) {
log.info("request[name:{}]", request.getName());
UserDetail userDetail = UserDetail.newBuilder()
.setId(1L)
.setName("zhangsan")
.setNick("张三")
.setAge(18)
.setEmail("zhangsan@gmail.com")
.setGender(1)
.setPhone("13000000000")
.build();
ApiResponse response = ApiResponse
.newBuilder()
.setCode("200")
.setMsg("success")
.setData(userDetail)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
配置文件
bootstrap.yml
server:
port: 8101
spring:
application:
name: @artifactId@
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
metadata:
version: v100
context-path: ${server.servlet.context-path}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yml
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
profiles:
active: @profiles.active@
application.yml
spring:
profiles:
active:
dev
mvc:
throw-exception-if-no-handler-found: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: NON-NULL
deserialization:
accept_empty_string_as_null_object: true
thymeleaf:
cache: false
grpc:
client:
GLOBAL:
negotiation-type: plaintext
enable-keep-alive: true
keep-alive-without-calls: true
3.2.3 boot-grpc-consumer
配置
pom.xml
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<!--swagger2 api在线文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>com.teddy</groupId>
<artifactId>boot-grpc-api</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
启动类
@EnableDiscoveryClient
@SpringBootApplication
public class BootGrpcConsumer {
public static void main(String[] args) {
SpringApplication.run(BootGrpcConsumer.class, args);
}
}
GRPC调用类
@Slf4j
@Service
public class UserServiceImpl {
private static final String SUCCESS_CODE = "200";
@GrpcClient("boot-grpc-provider")
private UserServiceGrpc.UserServiceBlockingStub stub;
public UserController.UserDetailVO findByName(String name) {
FindUserByNameRequest request = FindUserByNameRequest.newBuilder()
.setName(name)
.build();
ApiResponse response = stub.findByName(request);
checkResponse(response);
return convert2VO(response.getData());
}
private UserController.UserDetailVO convert2VO(UserDetail userDetail) {
return new UserController.UserDetailVO()
.setId(userDetail.getId())
.setName(userDetail.getName())
.setNick(userDetail.getNick())
.setAge(userDetail.getAge())
.setGender(userDetail.getGender())
.setPhone(userDetail.getPhone())
.setEmail(userDetail.getEmail());
}
private void checkResponse(ApiResponse response) {
if (!SUCCESS_CODE.equals(response.getCode())) {
throw new BizException(response.getCode(), response.getMsg());
}
}
}
门面类
@Api(value = "用户前端控制器", tags = "用户前端控制器")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserServiceImpl userService;
@ApiOperation("查找用户")
@PostMapping("/findByName")
public ApiResponse<UserDetailVO> sayHello(@Validated @RequestBody FindUserRequest request) {
return ApiResponseBuilderUtil.success(userService.findByName(request.getName()));
}
@Getter
@Setter
@ApiModel("查找用户请求参数")
private static class FindUserRequest {
@ApiModelProperty(value = "账户名")
private String name;
}
@ApiModel("用户信息")
@Getter
@Setter
@Accessors(chain = true)
public static class UserDetailVO {
private Long id;
private String name;
private String nick;
private Integer age;
private Integer gender;
private String phone;
private String email;
}
}
配置文件
bootstrap.yml
server:
port: 8111
spring:
application:
name: @artifactId@
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
metadata:
version: v100
context-path: ${server.servlet.context-path}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yml
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
profiles:
active: @profiles.active@
application.yml
spring:
profiles:
active:
dev
mvc:
throw-exception-if-no-handler-found: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: NON-NULL
deserialization:
accept_empty_string_as_null_object: true
thymeleaf:
cache: false
grpc:
client:
GLOBAL:
negotiation-type: plaintext
enable-keep-alive: true
keep-alive-without-calls: true
四、测试工具
grpc-swagger
五、优化
可以考虑将grpc
的请求和响应都抽象成通用对象和方法,proxy
代理客户端和服务端,使用Java IDL
,一般提供接口都是SDK,通过Java IDL
反向生成proto