ruoyi-vue-progRPC:高性能RPC框架集成
引言
在微服务架构盛行的今天,高性能的远程过程调用(Remote Procedure Call,RPC)框架已成为企业级应用的核心基础设施。ruoyi-vue-pro作为一款功能强大的后台管理系统,虽然已经集成了丰富的技术栈,但在gRPC(Google Remote Procedure Call)集成方面仍有巨大的优化空间。本文将深入探讨如何在ruoyi-vue-pro项目中集成gRPC框架,实现高性能的微服务通信。
什么是gRPC?
gRPC是一个高性能、开源和通用的RPC框架,由Google开发并基于HTTP/2协议标准设计。它使用Protocol Buffers(protobuf)作为接口定义语言(IDL),提供了以下核心优势:
- 高性能:基于HTTP/2的多路复用和二进制传输
- 跨语言支持:支持多种编程语言
- 强类型接口:通过protobuf定义服务契约
- 流式处理:支持单向和双向流式通信
ruoyi-vue-pro现有架构分析
当前技术栈
ruoyi-vue-pro项目基于Spring Boot 2.7.18构建,主要技术栈包括:
| 技术组件 | 版本 | 用途 |
|---|---|---|
| Spring Boot | 2.7.18 | 应用开发框架 |
| MyBatis Plus | 3.5.12 | ORM框架 |
| Redis + Redisson | 3.51.0 | 缓存和分布式锁 |
| Spring Security | 5.8.16 | 安全框架 |
| Flowable | 6.8.0 | 工作流引擎 |
现有通信方式
项目目前主要使用RESTful API进行服务间通信,虽然简单易用,但在高性能场景下存在以下局限性:
- HTTP/1.1协议的性能瓶颈
- JSON序列化/反序列化的开销
- 缺乏强类型约束
- 流式处理支持有限
gRPC集成方案设计
架构设计
依赖配置
在yudao-dependencies/pom.xml中添加gRPC相关依赖管理:
<!-- gRPC 相关依赖 -->
<grpc.version>1.64.0</grpc.version>
<protobuf.version>3.25.3</protobuf.version>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
Protobuf接口定义
创建用户服务的proto文件src/main/proto/user_service.proto:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "cn.iocoder.yudao.module.system.grpc";
option java_outer_classname = "UserServiceProto";
package system;
// 用户服务定义
service UserService {
// 根据ID获取用户信息
rpc GetUserById (GetUserRequest) returns (UserResponse) {}
// 分页查询用户列表
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse) {}
// 创建用户
rpc CreateUser (CreateUserRequest) returns (UserResponse) {}
// 更新用户
rpc UpdateUser (UpdateUserRequest) returns (UserResponse) {}
// 删除用户
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse) {}
}
// 请求消息定义
message GetUserRequest {
int64 user_id = 1;
}
message ListUsersRequest {
int32 page_num = 1;
int32 page_size = 2;
string username = 3;
string status = 4;
}
message CreateUserRequest {
string username = 1;
string password = 2;
string nickname = 3;
string email = 4;
string mobile = 5;
int32 dept_id = 6;
repeated int32 post_ids = 7;
repeated int32 role_ids = 8;
}
message UpdateUserRequest {
int64 user_id = 1;
string username = 2;
string nickname = 3;
string email = 4;
string mobile = 5;
int32 dept_id = 6;
repeated int32 post_ids = 7;
repeated int32 role_ids = 8;
string status = 9;
}
message DeleteUserRequest {
int64 user_id = 1;
}
// 响应消息定义
message UserResponse {
int64 user_id = 1;
string username = 2;
string nickname = 3;
string email = 4;
string mobile = 5;
int32 dept_id = 6;
string dept_name = 7;
repeated string post_names = 8;
repeated string role_names = 9;
string status = 10;
string create_time = 11;
}
message ListUsersResponse {
repeated UserResponse users = 1;
int32 total_count = 2;
int32 page_num = 3;
int32 page_size = 4;
}
message DeleteUserResponse {
bool success = 1;
string message = 2;
}
gRPC服务端实现
创建gRPC服务端实现类UserGrpcService.java:
package cn.iocoder.yudao.module.system.grpc;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO;
import cn.iocoder.yudao.module.system.convert.user.UserConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Slf4j
@GrpcService
public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {
@Autowired
private AdminUserService adminUserService;
@Override
public void getUserById(GetUserRequest request, StreamObserver<UserResponse> responseObserver) {
try {
AdminUserDO user = adminUserService.getUser(request.getUserId());
UserResponse response = convertToUserResponse(user);
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (Exception e) {
log.error("获取用户信息失败", e);
responseObserver.onError(e);
}
}
@Override
public void listUsers(ListUsersRequest request, StreamObserver<ListUsersResponse> responseObserver) {
try {
UserPageReqVO reqVO = new UserPageReqVO();
reqVO.setPageNo(request.getPageNum());
reqVO.setPageSize(request.getPageSize());
reqVO.setUsername(request.getUsername());
reqVO.setStatus(request.getStatus());
PageResult<AdminUserDO> pageResult = adminUserService.getUserPage(reqVO);
List<UserResponse> userResponses = pageResult.getList().stream()
.map(this::convertToUserResponse)
.collect(Collectors.toList());
ListUsersResponse response = ListUsersResponse.newBuilder()
.addAllUsers(userResponses)
.setTotalCount((int) pageResult.getTotal())
.setPageNum(request.getPageNum())
.setPageSize(request.getPageSize())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (Exception e) {
log.error("查询用户列表失败", e);
responseObserver.onError(e);
}
}
private UserResponse convertToUserResponse(AdminUserDO user) {
if (user == null) {
return UserResponse.getDefaultInstance();
}
return UserResponse.newBuilder()
.setUserId(user.getId())
.setUsername(user.getUsername())
.setNickname(user.getNickname())
.setEmail(user.getEmail())
.setMobile(user.getMobile())
.setStatus(user.getStatus().toString())
.setCreateTime(user.getCreateTime().toString())
.build();
}
}
gRPC客户端配置
创建gRPC客户端配置类GrpcClientConfig.java:
package cn.iocoder.yudao.framework.grpc.config;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GrpcClientConfig {
@Bean
public ManagedChannel managedChannel() {
return ManagedChannelBuilder.forAddress("localhost", 9090)
.usePlaintext()
.build();
}
@Bean
public UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub(ManagedChannel channel) {
return UserServiceGrpc.newBlockingStub(channel);
}
@Bean
public UserServiceGrpc.UserServiceStub userServiceStub(ManagedChannel channel) {
return UserServiceGrpc.newStub(channel);
}
}
服务端配置
创建gRPC服务端配置GrpcServerConfig.java:
package cn.iocoder.yudao.framework.grpc.config;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.io.IOException;
@Configuration
public class GrpcServerConfig {
@Autowired
private UserGrpcService userGrpcService;
private Server server;
@Bean
public Server grpcServer() throws IOException {
server = ServerBuilder.forPort(9090)
.addService(userGrpcService)
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (server != null) {
server.shutdown();
}
}));
return server;
}
@PreDestroy
public void destroy() {
if (server != null) {
server.shutdown();
}
}
}
性能优化策略
连接池管理
package cn.iocoder.yudao.framework.grpc.pool;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.stereotype.Component;
@Component
public class GrpcChannelPool {
private final GenericObjectPool<ManagedChannel> pool;
public GrpcChannelPool() {
GenericObjectPoolConfig<ManagedChannel> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(2);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
this.pool = new GenericObjectPool<>(new ChannelFactory(), config);
}
public ManagedChannel borrowChannel() throws Exception {
return pool.borrowObject();
}
public void returnChannel(ManagedChannel channel) {
pool.returnObject(channel);
}
private static class ChannelFactory extends BasePooledObjectFactory<ManagedChannel> {
@Override
public ManagedChannel create() throws Exception {
return ManagedChannelBuilder.forAddress("localhost", 9090)
.usePlaintext()
.build();
}
@Override
public PooledObject<ManagedChannel> wrap(ManagedChannel channel) {
return new DefaultPooledObject<>(channel);
}
@Override
public void destroyObject(PooledObject<ManagedChannel> p) throws Exception {
p.getObject().shutdown();
}
@Override
public boolean validateObject(PooledObject<ManagedChannel> p) {
return !p.getObject().isShutdown() && !p.getObject().isTerminated();
}
}
}
异步调用优化
package cn.iocoder.yudao.framework.grpc.async;
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.stub.StreamObserver;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
public class GrpcAsyncExecutor {
@Async("grpcTaskExecutor")
public <T> CompletableFuture<T> executeAsync(Supplier<ListenableFuture<T>> supplier) {
CompletableFuture<T> future = new CompletableFuture<>();
supplier.get().addListener(() -> {
try {
T result = supplier.get().get();
future.complete(result);
} catch (Exception e) {
future.completeExceptionally(e);
}
}, Runnable::run);
return future;
}
@Async("grpcTaskExecutor")
public <T> void executeStreaming(Supplier<StreamObserver<T>> supplier,
Consumer<StreamObserver<T>> consumer) {
StreamObserver<T> responseObserver = supplier.get();
try {
consumer.accept(responseObserver);
} catch (Exception e) {
responseObserver.onError(e);
}
}
}
监控与治理
健康检查
package cn.iocoder.yudao.framework.grpc.health;
import io.grpc.health.v1.HealthCheckRequest;
import io.grpc.health.v1.HealthCheckResponse;
import io.grpc.health.v1.HealthGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
@GrpcService
public class GrpcHealthService extends HealthGrpc.HealthImplBase {
private final HealthEndpoint healthEndpoint;
public GrpcHealthService(HealthEndpoint healthEndpoint) {
this.healthEndpoint = healthEndpoint;
}
@Override
public void check(HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {
HealthCheckResponse.ServingStatus status = convertStatus(healthEndpoint.health().getStatus());
HealthCheckResponse response = HealthCheckResponse.newBuilder()
.setStatus(status)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private HealthCheckResponse.ServingStatus convertStatus(Status status) {
if (status == Status.UP) {
return HealthCheckResponse.ServingStatus.SERVING;
} else {
return HealthCheckResponse.ServingStatus.NOT_SERVING;
}
}
}
指标监控
package cn.iocoder.yudao.framework.grpc.metrics;
import io.grpc.*;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
public class GrpcMetricsInterceptor implements ClientInterceptor {
private final MeterRegistry meterRegistry;
private final ConcurrentHashMap<String, Timer> timerCache = new ConcurrentHashMap<>();
public GrpcMetricsInterceptor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
long startTime = System.nanoTime();
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onClose(Status status, Metadata trailers) {
long duration = System.nanoTime() - startTime;
recordMetrics(method.getFullMethodName(), status.getCode(), duration);
super.onClose(status, trailers);
}
}, headers);
}
};
}
private void recordMetrics(String methodName, Status.Code statusCode, long durationNanos) {
String timerName = "grpc.client.requests";
Timer timer = timerCache.computeIfAbsent(methodName, key ->
Timer.builder(timerName)
.tag("method", methodName)
.tag("status", statusCode.toString())
.register(meterRegistry));
timer.record(durationNanos, TimeUnit.NANOSECONDS);
}
}
测试与验证
单元测试
package cn.iocoder.yudao.module.system.grpc;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.testing.GrpcCleanupRule;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
class UserGrpcServiceTest extends BaseDbUnitTest {
@Rule
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
@MockBean
private AdminUserService adminUserService;
private UserServiceGrpc.UserServiceBlockingStub blockingStub;
@BeforeEach
void setUp() throws Exception {
String serverName = InProcessServerBuilder.generateName();
UserGrpcService service = new UserGrpcService();
service.setAdminUserService(adminUserService);
grpcCleanup.register(InProcessServerBuilder
.forName(serverName)
.directExecutor()
.addService(service)
.build()
.start());
blockingStub = UserServiceGrpc.newBlockingStub(
grpcCleanup.register(InProcessChannelBuilder
.forName(serverName)
.directExecutor()
.build()));
}
@Test
void testGetUserById_Success() {
// 准备测试数据
AdminUserDO mockUser = new AdminUserDO();
mockUser.setId(1L);
mockUser.setUsername("testuser");
mockUser.setNickname("测试用户");
mockUser.setStatus(CommonStatusEnum.ENABLE.getStatus());
when(adminUserService.getUser(1L)).thenReturn(mockUser);
// 执行测试
GetUserRequest request = GetUserRequest.newBuilder().setUserId(1).build();
UserResponse response = blockingStub.getUserById(request);
// 验证结果
assertNotNull(response);
assertEquals(1L, response.getUserId());
assertEquals("testuser", response.getUsername());
assertEquals("测试用户", response.getNickname());
}
}
性能测试
package cn.iocoder.yudao.framework.grpc.performance;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
@SpringBootTest
@DirtiesContext
class GrpcPerformanceTest {
@Test
void testGrpcVsRestPerformance() {
// gRPC性能基准测试
long grpcStartTime = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
// gRPC调用
}
long grpcEndTime = System.currentTimeMillis();
// REST性能基准测试
long restStartTime = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
// REST调用
}
long restEndTime = System.currentTimeMillis();
System.out.println("gRPC耗时: " + (grpcEndTime - grpcStartTime) + "ms");
System.out.println("REST耗时: " + (restEndTime - restStartTime) + "ms");
}
}
部署与运维
Docker容器化
创建Dockerfile.grpc:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
# 安装grpc_health_probe用于健康检查
RUN wget -q -O /bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.24/grpc_health_probe-linux-amd64 && \
chmod +x /bin/grpc_health_probe
EXPOSE 9090 8080
ENTRYPOINT ["java","-jar","/app.jar"]
Kubernetes部署配置
创建grpc-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ruoyi-grpc-service
spec:
replicas: 3
selector:
matchLabels:
app: ruoyi-grpc
template:
metadata:
labels:
app: ruoyi-grpc
spec:
containers:
- name: grpc-server
image: ruoyi-grpc:latest
ports:
- containerPort: 9090
name: grpc
- containerPort: 8080
name: http
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:9090"]
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:9090"]
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ruoyi-grpc-service
spec:
selector:
app: ruoyi-grpc
ports:
- name: grpc
port: 9090
targetPort: 9090
- name: http
port: 8080
targetPort: 8080
type: ClusterIP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



