ruoyi-vue-progRPC:高性能RPC框架集成

ruoyi-vue-progRPC:高性能RPC框架集成

【免费下载链接】ruoyi-vue-pro 🔥 官方推荐 🔥 RuoYi-Vue 全新 Pro 版本,优化重构所有功能。基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 微信小程序,支持 RBAC 动态权限、数据权限、SaaS 多租户、Flowable 工作流、三方登录、支付、短信、商城、CRM、ERP、AI 大模型等功能。你的 ⭐️ Star ⭐️,是作者生发的动力! 【免费下载链接】ruoyi-vue-pro 项目地址: https://gitcode.com/GitHub_Trending/ruoy/ruoyi-vue-pro

引言

在微服务架构盛行的今天,高性能的远程过程调用(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 Boot2.7.18应用开发框架
MyBatis Plus3.5.12ORM框架
Redis + Redisson3.51.0缓存和分布式锁
Spring Security5.8.16安全框架
Flowable6.8.0工作流引擎

现有通信方式

项目目前主要使用RESTful API进行服务间通信,虽然简单易用,但在高性能场景下存在以下局限性:

  • HTTP/1.1协议的性能瓶颈
  • JSON序列化/反序列化的开销
  • 缺乏强类型约束
  • 流式处理支持有限

gRPC集成方案设计

架构设计

mermaid

依赖配置

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

【免费下载链接】ruoyi-vue-pro 🔥 官方推荐 🔥 RuoYi-Vue 全新 Pro 版本,优化重构所有功能。基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 微信小程序,支持 RBAC 动态权限、数据权限、SaaS 多租户、Flowable 工作流、三方登录、支付、短信、商城、CRM、ERP、AI 大模型等功能。你的 ⭐️ Star ⭐️,是作者生发的动力! 【免费下载链接】ruoyi-vue-pro 项目地址: https://gitcode.com/GitHub_Trending/ruoy/ruoyi-vue-pro

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值