gRPC service utilities providing health checking, server reflection, channelz observability, and binary logging capabilities
—
Client-side health checking utilities for load balancers and connection management. Enables intelligent routing decisions based on server health and performance metrics.
Utility for enabling client-side health checking for LoadBalancers, allowing load balancers to automatically route traffic away from unhealthy servers.
/**
* Utility for enabling client-side health checking for LoadBalancers.
* Wraps existing load balancer factories with health checking capabilities.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5025")
public final class HealthCheckingLoadBalancerUtil {
/**
* Creates a health-checking-capable LoadBalancer
* @param factory The underlying load balancer factory to wrap
* @param helper The load balancer helper for managing subchannels
* @return LoadBalancer instance with health checking enabled
*/
public static LoadBalancer newHealthCheckingLoadBalancer(
LoadBalancer.Factory factory,
Helper helper
);
}Usage Examples:
import io.grpc.LoadBalancer;
import io.grpc.ManagedChannelBuilder;
import io.grpc.protobuf.services.HealthCheckingLoadBalancerUtil;
import io.grpc.util.RoundRobinLoadBalancerFactory;
// Create a health-checking round-robin load balancer
public class HealthAwareClient {
private final ManagedChannel channel;
public HealthAwareClient(String target) {
// Create channel with health-checking load balancer
this.channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("health_checking_round_robin")
.usePlaintext()
.build();
}
// Alternative: Programmatic load balancer configuration
public HealthAwareClient(String target, LoadBalancer.Factory baseFactory) {
LoadBalancerRegistry registry = LoadBalancerRegistry.getDefaultRegistry();
// Register health-checking variant
registry.register(new LoadBalancer.Factory() {
@Override
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
return HealthCheckingLoadBalancerUtil.newHealthCheckingLoadBalancer(
baseFactory, helper);
}
@Override
public String getPolicyName() {
return "health_checking_custom";
}
});
this.channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("health_checking_custom")
.usePlaintext()
.build();
}
}Health checking load balancers work seamlessly with service discovery systems:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.protobuf.services.HealthCheckingLoadBalancerUtil;
public class ConsulAwareHealthCheckingClient {
private final ManagedChannel channel;
public ConsulAwareHealthCheckingClient() {
// Service discovery target that resolves to multiple endpoints
String consulTarget = "consul://user-service";
this.channel = ManagedChannelBuilder.forTarget(consulTarget)
// Health checking will verify each discovered endpoint
.defaultLoadBalancingPolicy("health_checking_round_robin")
.usePlaintext()
.build();
}
public void makeRequest() {
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
try {
// Request will be routed only to healthy servers
GetUserResponse response = stub.getUser(
GetUserRequest.newBuilder()
.setUserId(12345)
.build()
);
System.out.println("User retrieved: " + response.getUser().getName());
} catch (StatusRuntimeException e) {
System.err.println("Request failed: " + e.getStatus());
}
}
}public class KubernetesHealthCheckingClient {
private final ManagedChannel channel;
public KubernetesHealthCheckingClient(String serviceName, String namespace) {
// Kubernetes DNS target
String kubernetesTarget = String.format("dns:///%s.%s.svc.cluster.local:8080",
serviceName, namespace);
this.channel = ManagedChannelBuilder.forTarget(kubernetesTarget)
.defaultLoadBalancingPolicy("health_checking_round_robin")
// Use keep-alive for better health detection
.keepAliveTime(30, TimeUnit.SECONDS)
.keepAliveTimeout(5, TimeUnit.SECONDS)
.keepAliveWithoutCalls(true)
.usePlaintext()
.build();
}
public void shutdown() {
channel.shutdown();
try {
if (!channel.awaitTermination(60, TimeUnit.SECONDS)) {
channel.shutdownNow();
}
} catch (InterruptedException e) {
channel.shutdownNow();
Thread.currentThread().interrupt();
}
}
}import io.grpc.ChannelLogger;
import io.grpc.ConnectivityState;
import io.grpc.LoadBalancer;
public class AdvancedHealthCheckingClient {
public static class CustomHealthCheckingFactory extends LoadBalancer.Factory {
private final LoadBalancer.Factory delegate;
private final String healthServiceName;
public CustomHealthCheckingFactory(LoadBalancer.Factory delegate,
String healthServiceName) {
this.delegate = delegate;
this.healthServiceName = healthServiceName;
}
@Override
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
// Custom helper that configures health checking
LoadBalancer.Helper healthCheckingHelper = new LoadBalancer.Helper() {
@Override
public ManagedChannel createSubchannel(
CreateSubchannelArgs args) {
// Configure health checking for each subchannel
CreateSubchannelArgs.Builder argsBuilder = args.toBuilder();
// Add health check configuration
Map<String, Object> healthCheckConfig = new HashMap<>();
healthCheckConfig.put("serviceName", healthServiceName);
// Apply health check configuration to subchannel
return helper.createSubchannel(argsBuilder.build());
}
// Delegate other methods to original helper
@Override
public void updateBalancingState(ConnectivityState newState,
SubchannelPicker newPicker) {
helper.updateBalancingState(newState, newPicker);
}
@Override
public ChannelLogger getChannelLogger() {
return helper.getChannelLogger();
}
// ... other delegated methods
};
return HealthCheckingLoadBalancerUtil.newHealthCheckingLoadBalancer(
delegate, healthCheckingHelper);
}
@Override
public String getPolicyName() {
return "custom_health_checking";
}
}
public static ManagedChannel createHealthCheckingChannel(String target,
String serviceName) {
LoadBalancer.Factory baseFactory = new RoundRobinLoadBalancerFactory();
LoadBalancer.Factory healthCheckingFactory =
new CustomHealthCheckingFactory(baseFactory, serviceName);
LoadBalancerRegistry.getDefaultRegistry()
.register(healthCheckingFactory);
return ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("custom_health_checking")
.usePlaintext()
.build();
}
}public class HealthCheckMonitor {
private final ManagedChannel channel;
private final ScheduledExecutorService scheduler;
public HealthCheckMonitor(ManagedChannel channel) {
this.channel = channel;
this.scheduler = Executors.newScheduledThreadPool(1);
}
public void startMonitoring() {
scheduler.scheduleAtFixedRate(this::checkChannelHealth, 0, 10, TimeUnit.SECONDS);
}
private void checkChannelHealth() {
ConnectivityState state = channel.getState(false);
switch (state) {
case READY:
System.out.println("Channel is healthy and ready");
break;
case TRANSIENT_FAILURE:
System.out.println("Channel experiencing transient failure");
break;
case SHUTDOWN:
System.out.println("Channel is shutdown");
break;
case IDLE:
System.out.println("Channel is idle");
// Trigger connection attempt
channel.getState(true);
break;
case CONNECTING:
System.out.println("Channel is connecting");
break;
}
}
public void shutdown() {
scheduler.shutdown();
}
}Health checking load balancers can be combined with metrics collection for comprehensive observability:
public class MetricsAwareHealthCheckingClient {
private final ManagedChannel channel;
private final CallMetricRecorder metricsRecorder;
public MetricsAwareHealthCheckingClient(String target) {
this.channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("health_checking_round_robin")
// Interceptor to record client-side metrics
.intercept(new ClientMetricsInterceptor())
.usePlaintext()
.build();
}
private static class ClientMetricsInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
private long startTime = System.nanoTime();
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onClose(Status status, Metadata trailers) {
long duration = System.nanoTime() - startTime;
double durationMs = duration / 1_000_000.0;
// Record client-side metrics
if (status.isOk()) {
System.out.println("Successful call duration: " + durationMs + "ms");
} else {
System.out.println("Failed call duration: " + durationMs + "ms, " +
"Status: " + status.getCode());
}
super.onClose(status, trailers);
}
}, headers);
}
};
}
}
public void makeHealthyRequest() {
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
try {
// This request will only go to healthy servers
// thanks to the health checking load balancer
GetUserResponse response = stub.getUser(
GetUserRequest.newBuilder().setUserId(123).build());
System.out.println("Request successful: " + response.getUser().getName());
} catch (StatusRuntimeException e) {
System.err.println("Request failed even with health checking: " +
e.getStatus().getCode());
}
}
}public class RobustHealthCheckingClient {
private final ManagedChannel channel;
public RobustHealthCheckingClient(String target) {
this.channel = ManagedChannelBuilder.forTarget(target)
.defaultLoadBalancingPolicy("health_checking_round_robin")
// Configure connection parameters for better health detection
.keepAliveTime(30, TimeUnit.SECONDS)
.keepAliveTimeout(5, TimeUnit.SECONDS)
.keepAliveWithoutCalls(true)
// Configure retry behavior
.enableRetry()
.maxRetryAttempts(3)
.usePlaintext()
.build();
}
public void gracefulShutdown() {
System.out.println("Initiating graceful shutdown...");
channel.shutdown();
try {
// Wait for ongoing calls to complete
if (!channel.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("Forcing shutdown...");
channel.shutdownNow();
// Final wait for forced shutdown
if (!channel.awaitTermination(10, TimeUnit.SECONDS)) {
System.err.println("Channel did not terminate cleanly");
}
}
} catch (InterruptedException e) {
System.out.println("Shutdown interrupted, forcing immediate shutdown");
channel.shutdownNow();
Thread.currentThread().interrupt();
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-grpc--grpc-services