How to Benchmark gRPC Blocking Stubs in Java: Four Test Models Explained

This article walks through building a gRPC server and client in Java, then demonstrates four performance‑testing models—static thread, static QPS, dynamic thread, and dynamic QPS—detailing the code, configuration, and execution steps for each, while explaining why the blocking stub is preferred for metric collection.

FunTester
FunTester
FunTester
How to Benchmark gRPC Blocking Stubs in Java: Four Test Models Explained

Server

The server reuses the SDK from the previous fun_grpc project. It creates a fixed thread pool of size 10 named "gRPC", builds a gRPC server on port 12345, registers HelloServiceImpl, starts and awaits termination.

package com.funtester.grpc;

import com.funtester.frame.execute.ThreadPoolUtil;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;

public class Service {
    public static void main(String[] args) throws IOException, InterruptedException {
        ThreadPoolExecutor pool = ThreadPoolUtil.createFixedPool(10, "gRPC");
        Server server = ServerBuilder
                .forPort(12345)
                .executor(pool)
                .addService(new HelloServiceImpl())
                .build();
        server.start();
        server.awaitTermination();
    }
}

The service implementation sleeps for one second to simulate business latency, logs the incoming user name, and returns a greeting with the current date.

package com.funtester.grpc;

import com.funtester.frame.SourceCode;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloResponse;
import com.funtester.fungrpc.HelloServiceGrpc;
import com.funtester.utils.Time;
import io.grpc.stub.StreamObserver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    private static final Logger logger = LogManager.getLogger(HelloServiceImpl.class);

    @Override
    public void executeHi(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        HelloResponse response = HelloResponse.newBuilder()
                .setMsg("你好 " + request.getName() + Time.getDate())
                .build();
        SourceCode.sleep(1.0);
        logger.info("用户{}来了", request.getName());
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

Client

The client implementation follows the pattern shown in earlier articles and is omitted for brevity.

Static models

A static model defines the entire execution plan before the test starts, without runtime adjustments. Two common static models are presented.

Thread model

This model launches a fixed number of threads that repeatedly invoke the blocking stub.

package com.funtest.grpc;

import com.funtester.base.constaint.FixedThread;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.Concurrent;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

class FixedThreadModel extends SourceCode {
    static int times;
    static HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub;
    static HelloRequest requst;

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 12345)
                .usePlaintext().build();
        helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel).withCompression("gzip");
        requst = HelloRequest.newBuilder()
                .setName("FunTester")
                .build();
        RUNUP_TIME = 0;
        times = 2000;
        new Concurrent(new FunTester(), 10, "静态线程模型").start();
        managedChannel.shutdown();
    }

    private static class FunTester extends FixedThread {
        FunTester() {
            super(null, times, true);
        }
        @Override
        protected void doing() throws Exception {
            helloServiceBlockingStub.executeHi(requst);
        }
        @Override
        FunTester clone() {
            return new FunTester();
        }
    }
}

QPS model

This model controls request rate using a helper that defines target QPS and duration.

package com.funtest.grpc;

import com.funtester.base.event.FunCount;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.FunEventConcurrent;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

class FixedQpsModel extends SourceCode {
    static HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub;
    static HelloRequest requst;

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 12345)
                .usePlaintext().build();
        helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel).withCompression("gzip");
        requst = HelloRequest.newBuilder()
                .setName("FunTester")
                .build();
        def count = new FunCount(1, 1, 2, 1000, 10, "静态QPS模型");
        def test = {
            helloServiceBlockingStub.executeHi(requst);
        };
        new FunEventConcurrent(test, count).start();
        managedChannel.shutdown();
    }
}

Dynamic models

Dynamic models start with a minimal pressure (typically 1 QPS or 1 thread) and adjust the load during execution, allowing unrestricted test duration.

Dynamic thread model

Because the test runs indefinitely, the channel is not closed.

package com.funtest.grpc;

import com.funtester.base.constaint.FunThread;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.FunConcurrent;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.atomic.AtomicInteger;

class FunThreadModel extends SourceCode {
    static int times;
    static HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub;
    static HelloRequest requst;
    static AtomicInteger index = new AtomicInteger(0);
    static def desc = "动态线程模型";

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 12345)
                .usePlaintext().build();
        helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel).withCompression("gzip");
        requst = HelloRequest.newBuilder()
                .setName("FunTester")
                .build();
        new FunConcurrent(new FunTester()).start();
    }

    private static class FunTester extends FunThread {
        FunTester() {
            super(null, desc + index.getAndIncrement());
        }
        @Override
        protected void doing() throws Exception {
            helloServiceBlockingStub.executeHi(requst);
        }
        @Override
        FunTester clone() {
            return new FunTester();
        }
    }
}

Dynamic QPS model

This model adapts to most load‑testing needs while keeping the implementation simple.

package com.funtest.grpc;

import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.FunQpsConcurrent;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

class FunQpsModel extends SourceCode {
    static HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub;
    static HelloRequest requst;

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 12345)
                .usePlaintext().build();
        helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel).withCompression("gzip");
        requst = HelloRequest.newBuilder()
                .setName("FunTester")
                .build();
        def test = {
            helloServiceBlockingStub.executeHi(requst);
        };
        new FunQpsConcurrent(test).start();
    }
}

These four gRPC blocking‑client models provide a foundation for further performance experiments and monitoring in real‑world services.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaPerformance TestinggRPCLoad TestingBlocking Stub
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.