grpc load balancer integrated with etcd for Node.js
npm i grpclb grpc
grpclb
listsgrpc
as itspeerDependency
notdependency
because here
import { register } from 'grpclb';
const revoke = await register({
server, // your grpc server instance
etcdKV: { key, value }, // leave you to decide how to serialize service name, host, port into KV
ttl: 10, // default 10 in seconds
etcdHosts: hosts, // etcd hosts, or you can set as env var ETCD_HOSTS
});
// then you can revoke
// by direct call the revoke handler
revoke();
// or by shutting down the grpc server
server.tryShutdown(() => {});
- client-side load balancing with
round-robin strategy
We can't register custom service resolver util the C
library exposes the api.
So we can implement client-side load-balancing on the other way: javascript Proxy
import { createGrpcProxy } from 'grpclb';
import { loadSync } from '@grpc/proto-loader';
import { loadPackageDefinition } from 'grpc';
// load .proto file
const packageDefinition = loadSync(PROTO_PATH);
// initialize into javascript object
const pkgDef = loadPackageDefinition(packageDefinition);
const proxy = await createGrpcProxy({
etcdHosts: hosts, // etcd hosts, or you can set as env var ETCD_HOSTS
target: pkgDef.helloworld, // your gRPC object, MUST be the package definition object
parseKV, // how to extract service name, host, port from etcd key and value
});
// Every time you access the service object, you get the new servant address.
const servant = proxy.Greeter;
// The service was already initialized and
// you can just call the service method to send request
servant.sayHello({ name }, (error, response) => {});
The Proxy
way is not convenient. So grpclb
also provides another api to do the load balancing:
import { createClientPool } from 'grpclb';
import { GreeterClient } from 'helloworld/static_codegen/helloworld_grpc_pb';
import { HelloRequest } from 'helloworld/static_codegen/helloworld_pb';
const pool = await createClientPool({
Client: GreeterClient, // your client service
parseKV, // how to extract service name, host, port from etcd key and value
etcdHosts: hosts, // etcd hosts, or you can set as env var ETCD_HOSTS
});
// Every time you access the service object, you get the new servant address.
const servant = pool.get();
// The service was already initialized and
// you can just call the service method to send request
servant.sayHello(new HelloRequest(), (error, response) => {});
Image you have two copies of grpc
, it would look like:
├── node_modules
│ ├── grpclb
│ │ └── node_modules
│ │ └── grpc
│ └── grpc
└── src
└── static_codegen
├── helloworld_grpc_pb.js
├── helloworld_pb.d.ts
└── helloworld_pb.js
require('grpc')
in src directory, no matter dynamic generated gRPC javascript code or static generated, would resolve tonode_modules/grpc
require('grpc')
ingrpclb
package would resolve tonode_modules/grpclb/node_modules/grpc
Then initialization would throw error
TypeError: Channel's second argument must be a ChannelCredentials
. See details for this issue
List grpc
as peerDependency can avoid this situation.