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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
