mirror of https://github.com/status-im/consul.git
Website: GH-730 and cleanup for docs/guides/semaphore.html
This commit is contained in:
parent
c1e4eb2f2c
commit
3f971e694b
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue