diff --git a/website/source/docs/guides/semaphore.html.markdown b/website/source/docs/guides/semaphore.html.markdown index 27305ab8ca..36029a6113 100644 --- a/website/source/docs/guides/semaphore.html.markdown +++ b/website/source/docs/guides/semaphore.html.markdown @@ -8,36 +8,39 @@ description: |- # Semaphore -The goal of this guide is to cover how to build a client-side semaphore using Consul. -This is useful when you want to coordinate many services while restricting access to -certain resources. +This guide demonstrates how to implement a distributed semaphore using the Consul +Key/Value store. This is useful when you want to coordinate many services while +restricting access to certain resources. -If you only need mutual exclusion or leader election, [this guide](/docs/guides/leader-election.html) +~> If you only need mutual exclusion or leader election, +[this guide](/docs/guides/leader-election.html) provides a simpler algorithm that can be used instead. There are a number of ways that a semaphore can be built, so our goal is not to cover all the possible methods. Instead, we will focus on using Consul's support for -[sessions](/docs/internals/sessions.html), which allow us to build a system that can -gracefully handle failures. +[sessions](/docs/internals/sessions.html). Sessions allow us to build a system that +can gracefully handle failures. Note that JSON output in this guide has been pretty-printed for easier reading. Actual values returned from the API will not be formatted. ## Contending Nodes -The primary flow is for nodes who are attempting to acquire a slot in the semaphore. -All nodes that are participating should agree on a given prefix being used to coordinate, -a single lock key, and a limit of slot holders. A good choice is simply: +Let's imagine we have a set of nodes who are attempting to acquire a slot in the +semaphore. All nodes that are participating should agree on three decisions: the +prefix in the Key/Value store used to coordinate, a single key to use as a lock, +and a limit on the number of slot holders. + +For the prefix we will be using for coordination, a good pattern is simply: ```text service//lock/ ``` -We will refer to this as just `` for simplicity. - -The first step is to create a session. This is done using the [/v1/session/create endpoint][session-api]: +We'll abbreviate this pattern as simply `` for the rest of this guide. -[session-api]: http://www.consul.io/docs/agent/http.html#_v1_session_create +The first step is to create a session. This is done using the +[Session HTTP API](/docs/agent/http/session.html#session_create): ```text curl -X PUT -d '{"Name": "dbservice"}' \ @@ -52,31 +55,32 @@ This will return a JSON object contain the session ID: } ``` -The session by default makes use of only the gossip failure detector. Additional checks -can be specified if desired. +Next, we create a contender entry. Each contender creates an entry that is tied +to a session. This is done so that if a contender is holding a slot and fails, +it can be detected by the other contenders. -Next, we create a contender entry. Each contender makes an entry that is tied -to a session. This is done so that if a contender is holding a slot and fails -it can be detected by the other contenders. Optionally, an opaque value -can be associated with the contender via a ``. - -Create the contender key by doing an `acquire` on `/` by doing a `PUT`. +Create the contender key by doing an `acquire` on `/` via `PUT`. This is something like: ```text curl -X PUT -d http://localhost:8500/v1/kv//?acquire= ``` -Where `` is the ID returned by the call to `/v1/session/create`. +The `` value is the ID returned by the call to +[`/v1/session/create`]((/docs/agent/http/session.html#session_create). + +`body` can be used to associate a meaningful value with the contender. This is opaque +to Consul but can be useful for human operators. -This will either return `true` or `false`. If `true` is returned, the contender -entry has been created. If `false` is returned, the contender node was not created and -likely this indicates a session invalidation. +The call will either return `true` or `false`. If `true`, the contender entry has been +created. If `false`, the contender node was not created; it'slikely that this indicates +a session invalidation. The next step is to use a single key to coordinate which holders are currently -reserving a slot. A good choice is simply `/.lock`. We will refer to this -special coordinating key as ``. The current state of the semaphore is read by -doing a `GET` on the entire ``: +reserving a slot. A good choice for this lock key is simply `/.lock`. We will +refer to this special coordinating key as ``. + +The current state of the semaphore is read by doing a `GET` on the entire ``: ```text curl http://localhost:8500/v1/kv/?recurse @@ -100,7 +104,7 @@ is used to detect a potential conflict. The next step is to determine which of t slot holders are still alive. As part of the results of the `GET`, we have all the contender entries. By scanning those entries, we create a set of all the `Session` values. Any of the `Holders` that are not in that set are pruned. In effect, we are creating a set of live contenders -based on the list results, and doing a set difference with the `Holders` to detect and prune +based on the list results and doing a set difference with the `Holders` to detect and prune any potentially failed holders. If the number of holders (after pruning) is less than the limit, a contender attempts acquisition @@ -113,21 +117,24 @@ This is done by: curl -X PUT -d http://localhost:8500/v1/kv/?cas= ``` -If this suceeds with `true` the contender now holds a slot in the semaphore. If this fails +If this suceeds with `true`, the contender now holds a slot in the semaphore. If this fails with `false`, then likely there was a race with another contender to acquire the slot. Both code paths now go into an idle waiting state. In this state, we watch for changes on ``. This is because a slot may be released, a node may fail, etc. -Slot holders must also watch for changes since the slot may be released by an operator, +Slot holders must also watch for changes since the slot may be released by an operator or automatically released due to a false positive in the failure detector. -Watching for changes is done by doing a blocking query against ``. If a contender +Note that the session by default makes use of only the gossip failure detector. That +is, the session is considered held by a node as long as the default Serf health check +has not declared the node unhealthy. Additional checks can be specified if desired. + +Watching for changes is done via a blocking query against ``. If a contender holds a slot, then on any change the `` should be re-checked to ensure the slot is still held. If no slot is held, then the same acquisition logic is triggered to check and potentially re-attempt acquisition. This allows a contender to steal the slot from a failed contender or one that has voluntarily released its slot. If a slot holder ever wishes to release voluntarily, this should be done by doing a -Check-And-Set operation against `` to remove its session from the `Holders`. Once -that is done, the contender entry at `/` should be delete. Finally the -session should be destroyed. - +Check-And-Set operation against `` to remove its session from the `Holders` object. +Once that is done, the contender entry at `/` should be deleted. Finally, +the session should be destroyed.