github.com/projecteru2/core/client is the Go client other services use to talk to core. It is
a thin wrapper around the generated pb.CoreRPCClient plus the pieces that make a multi-instance
deployment usable: address resolution, load balancing, retry of the watch streams, and
authentication.
import (
"github.com/projecteru2/core/client"
pb "github.com/projecteru2/core/rpc/gen"
"github.com/projecteru2/core/types"
)
cli, err := client.NewClient(ctx, "127.0.0.1:5001", types.AuthConfig{})
if err != nil {
return err
}
rpc := cli.GetRPCClient()
info, err := rpc.Info(ctx, &pb.Empty{})
GetRPCClient() returns the generated CoreRPCClient. The connection is
configured with insecure transport credentials, a 6-minute keepalive ping with a 1-second
timeout, and round_robin load balancing across whatever the resolver produced.
If core requires auth, pass the same username and password as its auth config; the client
installs per-RPC credentials that send them as one metadata pair. Note this travels in the clear —
the client does not enable TLS.
Importing client registers two gRPC resolvers, so addr may be:
| Target | Meaning |
|---|---|
host:port |
one instance, no resolution |
static://_/addr1,addr2,addr3 |
a fixed set of instances, round-robined |
eru:///addr |
bootstrap from one instance, then follow service discovery |
lb://_/addr |
internal: the target servicediscovery dials, kept in step with the pushed endpoint list |
eru:// is the interesting one: the client connects to the given address, subscribes to
WatchServiceStatus, and rewrites the connection’s address list every time core pushes a new set.
Instances that come up join the round-robin pool, instances that go away leave it — no restart, no
config change. Credentials are passed as the types.AuthConfig argument to NewClient, not in the URL.
For callers that would rather hold several independent connections and pick a live one:
pool, err := client.NewCoreRPCClientPool(ctx, &client.PoolConfig{
EruAddrs: []string{"10.0.0.1:5001", "10.0.0.2:5001"},
Auth: types.AuthConfig{},
ConnectionTimeout: 10 * time.Second,
})
if err != nil {
return err
}
rpc := pool.GetClient()
The pool dials every address at construction, drops the ones that fail, and returns
ErrAllConnectionsFailed if none answered. A background loop re-probes every client with Info
every 2 × ConnectionTimeout; GetClient returns the first client currently marked alive, or the
first client at all if everything is down.
If you only want the address list — to feed your own balancer, say:
import "github.com/projecteru2/core/client/servicediscovery"
sd := servicediscovery.New("127.0.0.1:5001", types.AuthConfig{})
ch, err := sd.Watch(ctx)
for addresses := range ch {
// current set of live core instances
}
Watch reconnects on its own: if the stream dies it retries after 10 seconds, and if no push
arrives within the interval core advertised (twice grpc.service_discovery_interval) it cancels
and re-establishes the watch rather than sitting on a silent connection.
The client installs both a unary and a stream retry interceptor. Both default to
Max: 0 — no retries — because a resource operation is not safe to replay blindly.
The stream interceptor only wraps two methods, which are pure watches and therefore replayable:
/pb.CoreRPC/WatchServiceStatus/pb.CoreRPC/WorkloadStatusStreamFor those, a broken stream is re-established with exponential backoff and the last sent message is replayed. The service-discovery client raises the limit to 1 for its own watch.