Website: GH-730 and cleanup for docs/guides/semaphore.html

This commit is contained in:
Ryan Breen 2015-03-01 00:18:21 -05:00
parent c1e4eb2f2c
commit 3f971e694b
1 changed files with 43 additions and 36 deletions

View File

@ -8,36 +8,39 @@ description: |-
# Semaphore # Semaphore
The goal of this guide is to cover how to build a client-side semaphore using Consul. This guide demonstrates how to implement a distributed semaphore using the Consul
This is useful when you want to coordinate many services while restricting access to Key/Value store. This is useful when you want to coordinate many services while
certain resources. 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. 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 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 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 [sessions](/docs/internals/sessions.html). Sessions allow us to build a system that
gracefully handle failures. can gracefully handle failures.
Note that JSON output in this guide has been pretty-printed for easier Note that JSON output in this guide has been pretty-printed for easier
reading. Actual values returned from the API will not be formatted. reading. Actual values returned from the API will not be formatted.
## Contending Nodes ## Contending Nodes
The primary flow is for nodes who are attempting to acquire a slot in the semaphore. Let's imagine we have a set of nodes who are attempting to acquire a slot in the
All nodes that are participating should agree on a given prefix being used to coordinate, semaphore. All nodes that are participating should agree on three decisions: the
a single lock key, and a limit of slot holders. A good choice is simply: 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 ```text
service/<service name>/lock/ service/<service name>/lock/
``` ```
We will refer to this as just `<prefix>` for simplicity. We'll abbreviate this pattern as simply `<prefix>` for the rest of this guide.
The first step is to create a session. This is done using the [/v1/session/create endpoint][session-api]: The first step is to create a session. This is done using the
[Session HTTP API](/docs/agent/http/session.html#session_create):
[session-api]: http://www.consul.io/docs/agent/http.html#_v1_session_create
```text ```text
curl -X PUT -d '{"Name": "dbservice"}' \ 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 Next, we create a contender entry. Each contender creates an entry that is tied
can be specified if desired. 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 Create the contender key by doing an `acquire` on `<prefix>/<session>` via `PUT`.
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 `<body>`.
Create the contender key by doing an `acquire` on `<prefix>/<session>` by doing a `PUT`.
This is something like: This is something like:
```text ```text
curl -X PUT -d <body> http://localhost:8500/v1/kv/<prefix>/<session>?acquire=<session> curl -X PUT -d <body> http://localhost:8500/v1/kv/<prefix>/<session>?acquire=<session>
``` ```
Where `<session>` is the ID returned by the call to `/v1/session/create`. The `<session>` value is the ID returned by the call to
[`/v1/session/create`]((/docs/agent/http/session.html#session_create).
This will either return `true` or `false`. If `true` is returned, the contender `body` can be used to associate a meaningful value with the contender. This is opaque
entry has been created. If `false` is returned, the contender node was not created and to Consul but can be useful for human operators.
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 The next step is to use a single key to coordinate which holders are currently
reserving a slot. A good choice is simply `<prefix>/.lock`. We will refer to this reserving a slot. A good choice for this lock key is simply `<prefix>/.lock`. We will
special coordinating key as `<lock>`. The current state of the semaphore is read by refer to this special coordinating key as `<lock>`.
doing a `GET` on the entire `<prefix>`:
The current state of the semaphore is read by doing a `GET` on the entire `<prefix>`:
```text ```text
curl http://localhost:8500/v1/kv/<prefix>?recurse curl http://localhost:8500/v1/kv/<prefix>?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 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 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 `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. any potentially failed holders.
If the number of holders (after pruning) is less than the limit, a contender attempts acquisition 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 <Updated Lock> http://localhost:8500/v1/kv/<lock>?cas=<lock-modify-index> curl -X PUT -d <Updated Lock> http://localhost:8500/v1/kv/<lock>?cas=<lock-modify-index>
``` ```
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. 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 Both code paths now go into an idle waiting state. In this state, we watch for changes
on `<prefix>`. This is because a slot may be released, a node may fail, etc. on `<prefix>`. 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. or automatically released due to a false positive in the failure detector.
Watching for changes is done by doing a blocking query against `<prefix>`. 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 `<prefix>`. If a contender
holds a slot, then on any change the `<lock>` should be re-checked to ensure the slot is holds a slot, then on any change the `<lock>` 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 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 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. 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 If a slot holder ever wishes to release voluntarily, this should be done by doing a
Check-And-Set operation against `<lock>` to remove its session from the `Holders`. Once Check-And-Set operation against `<lock>` to remove its session from the `Holders` object.
that is done, the contender entry at `<prefix>/<session>` should be delete. Finally the Once that is done, the contender entry at `<prefix>/<session>` should be deleted. Finally,
session should be destroyed. the session should be destroyed.