gRPC SkyLB
: an external gRPC load balancer based on External Load Balancing Service. Implemented in the Java language. The design follows the gRPC Load Balancer Architecture (https://github.com/grpc/grpc/blob/master/doc/load-balancing.md)
Different from traditional RPC systerm. gRPC is based on HTTP2 thus the recommended way to call an RPC is to create one long lived connect and use it for all clients (multiplexing and streaming).
It has the benefit of very high efficiency and throughput. However, it gives the traditional load balance architecture a difficulty to fit in. These architectures works nicely for ephemeral connections, but failed badly in gRPC.
To fix this issue, a load balancing architecture proposal was created by the gRPC dev team in Febrary, 2016 and reached its maturity around July, 2016. We are copied load-balancing.md and cached the proposal. The core module in this proposal is an external load balancer which aims at both flexibility and the simplicity for the gRPC client programming.
However, there is not an official implementation for the external load balancer (and probably there will not be an official one since every company has its distinct production environment). This is the reason we initiated the project gRPC-SkyLB
.
The design follows the gRPC "Load Balancer Architecture" workflow
As shown in the diagram, we will go into the implementation details below:
- All service info is saved in an etcd cluster
- One or more
SkyLB
servers talk to the etcd cluster SkyLB
service is highly available (more than one instances). Its load is rather light and stable- At the first step, both the gRPC clients and services ask
SkyLB
to resolve the location ofSkyLB
instances. OneSkyLB
instance will be randomly chosen - Target instance perspective
- After a
SkyLB
instance is chosen, the target service sends a gRPC call toSkyLB
with a streaming request - The request is treated as a heartbeat or load report. With a fixed interval, the target service keeps sending requests to
SkyLB
SkyLB
finds the remote address of the target instance, and saves it with other info (priority and weight) to etcd with a TTL
- After a
- Client instance perspective
- After a
SkyLB
instance is chosen, the gRPC client sends a gRPC call (with the target service name) toSkyLB
, and receives a streaming response SkyLB
should return a response right after the request arrives, and whenever the service changes- The response contains all services currently alive by the knowledge of
SkyLB
- The client establishes a gRPC connection to all target instances. When the client is notified for service change, it update the connections
- For each gRPC call, the client instance chooses one connection to send requests. We can use the built-in
RoundRobin
orConsistentHash
policy to choose connection. In future we might have more complex load balancing policies
- After a
After a client connects to the SkyLB
server, it starts a two-way gRPC stream call. The client then comes into a passive mode, waiting for instruction from the server. When the time comes (for example when user triggers it through UI), the server sends a specific request to the client, and the client executes the instruction and returns with a corresponding response.
NOTE
that whenever an error is caused during the streaming talk, the stream has to be discarded and a new stream should be created (by the client).
The API protocol is defined in github.com/binchencoder/grpc-skylb/skylb-proto/api.proto.
-
Dependency
Install skylb-client local repository
chenbindeMacBook-Pro:grpc-skylb chenbin$ mvn clean install
<dependency> <groupId>com.binchencoder.skylb</groupId> <artifactId>skylb-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
Implements gRPC API
public class DemoGrpcImpl extends DemoGrpc.DemoImplBase { private final Logger LOGGER = LoggerFactory.getLogger(DemoGrpcImpl.class); private Random rand = new Random(System.currentTimeMillis()); private int port; public DemoGrpcImpl(int port_) { this.port = port_; } @Override public void greeting(GreetingProtos.GreetingRequest request, StreamObserver<GreetingProtos.GreetingResponse> responseObserver) { LOGGER.info("Got req: {}", request); // 随机耗时350~550毫秒. int elapse = 350 + rand.nextInt(200); try { TimeUnit.MILLISECONDS.sleep(elapse); } catch (InterruptedException ie) { LOGGER.warn("sleep interrupted"); } GreetingResponse reply = GreetingResponse.newBuilder().setGreeting( "Hello " + request.getName() + ", from :" + port + ", elapse " + elapse + "ms").build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } @Override public void greetingForEver(GreetingProtos.GreetingRequest request, StreamObserver<GreetingProtos.GreetingResponse> responseObserver) { super.greetingForEver(request, responseObserver); } }
-
Register gRPC Server to SkyLB Server
Server server = ServerTemplate.create( {port} 9090, {bindableService} new DemoGrpcImpl(), {serviceName} "shared-test-client-service") .build() .start(); SkyLBServiceReporter reporter = ServerTemplate.reportLoad( {skylbUri} "skylb://127.0.0.1:1900/", {serviceName} ServiceNameUtil.toString(ServiceId.CUSTOM_EASE_GATEWAY_TEST), {portName} "grpc", {port} 9090);
-
Call gRPC Server
Create gRPC Stub
ManagedChannel channel = ClientTemplate.createChannel( {skylbAddr} "skylb://127.0.0.1:1900/", {calleeServiceName} ServiceNameUtil.toString(ServiceId.CUSTOM_EASE_GATEWAY_TEST), {calleePortName} "grpc", {calleeNamespace} null, {callerServiceName} ServiceNameUtil.toString(ServiceId.SERVICE_NONE)).getOriginChannel(); DemoGrpc.DemoBlockingStub blockingStub = DemoGrpc.newBlockingStub(channel);
GreetingRequest request = GreetingRequest.newBuilder() .setName("GC " + Calendar.getInstance().get(Calendar.SECOND)) .build(); GreetingResponse response = blockingStub .withDeadlineAfter(2000, TimeUnit.MILLISECONDS) .greeting(request);
-
Build SkyLB Server
chenbindeMacBook-Pro:grpc-skylb chenbin$ cd skylb-server chenbindeMacBook-Pro:skylb-server chenbin$ mvn clean package
最终skylb.jar 打包到 skylb-server/target 目录下
-
Start SkyLB Server
Start ETCD3
yum install etcd -y systemctl enable etcd && systemctl start etcd
NOTE
部署ETCD遇到问题可参考 https://www.cnblogs.com/shawhe/p/10640820.html
chenbindeMacBook-Pro:skylb-server chenbin$ cd target/skylblib chenbindeMacBook-Pro:skylblib chenbin$ java -jar skylb.jar -h Usage: java -jar skylb.jar [options] [command] [command options] Options: --auto-disconn-timeout, -auto-disconn-timeout The timeout to automatically disconnect the resolve RPC. e.g. 10s(10 Seconds), 10m(10 Minutes) Default: PT5M Commands: etcd Help for etcd options Usage: etcd [options] Options: --etcd-endpoints, -etcd-endpoints The comma separated ETCD endpoints. e.g., http://etcd1:2379,http://etcd2:2379 Default: [http://127.0.0.1:2379] --etcd-key-ttl, -etcd-key-ttl The etcd key time-to-live. e.g. 10s(10 Seconds), 10m(10 Minutes) Default: PT10S chenbindeMacBook-Pro:skylblib chenbin$ java -jar skylb.jar -etcd-endpoints=http://127.0.0.1:2379
-
Start gRPC Server
Run com/binchencoder/skylb/demo/grpc/server/GreetingServer#main
-
Start gRPC Client
Run com/binchencoder/skylb/demo/grpc/client/GreetingClient#main
grpc-skylb/examples/echo
该示例采用SpringBoot脚手架,在spring-boot-grpc-common.jar中封装好了注册服务的逻辑,启动方式比较简单
@GrpcService(applyGlobalInterceptors = true) // Default use Global Interceptor
public class EchoGrpcService extends EchoServiceGrpc.EchoServiceImplBase {
}
-
使用
@GrpcService
注解方式标示gRPC Service -
在 spring-boot-grpc-common.jar 中默认配置两个Global Interceptor(ExceptionInterceptor, AuthenticationInterceptor)
-
使用者可通过注解不使用Global Interceptor,实现自己的Interceptor
@GrpcService(applyGlobalInterceptors = false, interceptors = ExceptionInterceptor.class)
- @GrpcService Annotation
- 支持配置全局拦截器
- 将来计划支持skylb-client Golang版本