moonsd service discovery

Service discovery · MoonBit

moonsd

A service has to say it is here, keep saying it, and be found. moonsd writes that down as three traits — a registrar, a discovery, a watcher — and leaves every registry to implement them.

4modules 15packages 1847public items 4targets
the register rev 0 · ttl 8.0s
greeter 0 · checkout 0
01

Article one

Three traits, and nothing about any one registry

A registry is a place a service writes its address and a caller reads it back. That is all the contract fixes. What the registry does with a lapsed lease, how a watcher learns of a change, whether the store is a process, a cluster or a key-value server — none of it reaches the caller.

i

Registrar

register(service, endpoint, ttl) → Lease
renew(lease)
deregister(lease)

An address is known for its lease and no longer. Renewing extends it; a lease the registry has let go raises Refused::Expired.

ii

Discovery

resolve(service) → [Endpoint]
watch(service) → &Watcher

One answer now, or a stream of answers. A service nobody registered has no endpoints; that is not an error.

iii

Watcher

next() → Update
close()

Whole snapshots, never events. Reconnecting and recovering from a compacted history are the backend's business, so what comes out is what is true now.

02

Article two

A lease, and who keeps it

An endpoint is alive because something keeps saying so. hold renews at a third of the lease and registers again when the registry has forgotten it. View follows a service in one task and hands the latest list to whoever is on the request path, without a round trip.

LEASE TIME register renew renew lapse ttl ttl ttl gone
Nobody renewed the third lease, so the endpoint is off the register — and this is the only way an endpoint ever leaves unexpectedly.

Both are written once, against the traits, so a backend gets them by implementing six methods:

async fn serve(registry : &@moonsd.Registrar) -> Unit {
  let endpoint = @moonsd.Endpoint::new("10.0.0.1", 12000, metadata=Map([("zone", "a")]))
  @async.with_task_group(group => {
    group.spawn_bg(() => @moonsd.hold(registry, "greeter", endpoint, ttl=15_000))
    // serve here; leaving the group cancels hold, which deregisters
  })
}
03

Article three

One contract, four modules

The contract is a module of its own, and each registry is a client library that implements it — a full client for that registry, with a small sd package where the client meets the contract.

the contract 55 items

moonsd

The three traits, Endpoint, Lease, Update, hold, View, an in-process registry and the conformance suite.

moonbitstack/moonsd · memory · conformance
read the guide →
backend 562 items

moonredis

Keys that expire carry the endpoint, so a lapsed lease needs no sweeper. A watcher reads the key set again on an interval.

moonbitstack/moonredis · sd
the redis registry →
backend 908 items

moonetcd

Keys on leases, so the key goes when the lease does; the revision etcd stamps on every write is where a watcher resumes.

moonbitstack/moonetcd · sd
the etcd registry →
backend 322 items

moonconsul

Services with TTL checks, so other Consul clients see ordinary services; a watcher follows them with blocking queries.

moonbitstack/moonconsul · sd
the consul registry →
04

Article four

Held to the same behaviour

A backend is not finished when it compiles. conformance.check runs every behaviour the contract promises against one registrar and one discovery: the fields that survive a round trip, the lease that lapses, the watcher that hears a change and then hears the service empty. Each client's suite runs it against the real server, in CI.

BackendWhere the endpoint livesLease ends byWatcher learns byCheck
memorya map in this processa clock, and the next call that looksa queue per followerboth ways: with and without a clock
moonredisa key with a TTLRedis expiring the keyreading the key set againlive Redis 8, standalone and clustered
moonetcda key attached to a leasethe lease being revoked or losta watch from the last revisionlive etcd 3.5, three nodes, members killed
moonconsula service with a TTL checkthe check going critical and being reapeda blocking querylive Consul 1.20, two agents