You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
Fix typo and improve Sentinel docs (#2931)
This commit is contained in:
@@ -14,7 +14,7 @@ const sentinel = await createSentinel({
|
||||
port: 1234
|
||||
}]
|
||||
})
|
||||
.on('error', err => console.error('Redis Sentinel Error', err));
|
||||
.on('error', err => console.error('Redis Sentinel Error', err))
|
||||
.connect();
|
||||
|
||||
await sentinel.set('key', 'value');
|
||||
@@ -26,16 +26,19 @@ In the above example, we configure the sentinel object to fetch the configuratio
|
||||
|
||||
## `createSentinel` configuration
|
||||
|
||||
| Property | Default | Description |
|
||||
|-----------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| name | | The sentinel identifier for a particular database cluster |
|
||||
| sentinelRootNodes | | An array of root nodes that are part of the sentinel cluster, which will be used to get the topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the sentinel configuration from the server |
|
||||
| maxCommandRediscovers | `16` | The maximum number of times a command will retry due to topology changes. |
|
||||
| nodeClientOptions | | The configuration values for every node in the cluster. Use this for example when specifying an ACL user to connect with |
|
||||
| sentinelClientOptions | | The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with |
|
||||
| masterPoolSize | `1` | The number of clients connected to the master node |
|
||||
| replicaPoolSize | `0` | The number of clients connected to each replica node. When greater than 0, the client will distribute the load by executing read-only commands (such as `GET`, `GEOSEARCH`, etc.) across all the cluster nodes. |
|
||||
| reserveClient | `false` | When `true`, one client will be reserved for the sentinel object. When `false`, the sentinel object will wait for the first available client from the pool. |
|
||||
| Property | Default | Description |
|
||||
|----------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| name | | The sentinel identifier for a particular database cluster |
|
||||
| sentinelRootNodes | | An array of root nodes that are part of the sentinel cluster, which will be used to get the topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the sentinel configuration from the server |
|
||||
| maxCommandRediscovers | `16` | The maximum number of times a command will retry due to topology changes. |
|
||||
| nodeClientOptions | | The configuration values for every node in the cluster. Use this for example when specifying an ACL user to connect with |
|
||||
| sentinelClientOptions | | The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with |
|
||||
| masterPoolSize | `1` | The number of clients connected to the master node |
|
||||
| replicaPoolSize | `0` | The number of clients connected to each replica node. When greater than 0, the client will distribute the load by executing read-only commands (such as `GET`, `GEOSEARCH`, etc.) across all the cluster nodes. |
|
||||
| scanInterval | `10000` | Interval in milliseconds to periodically scan for changes in the sentinel topology. The client will query the sentinel for changes at this interval. |
|
||||
| passthroughClientErrorEvents | `false` | When `true`, error events from client instances inside the sentinel will be propagated to the sentinel instance. This allows handling all client errors through a single error handler on the sentinel instance. |
|
||||
| reserveClient | `false` | When `true`, one client will be reserved for the sentinel object. When `false`, the sentinel object will wait for the first available client from the pool. |
|
||||
|
||||
## PubSub
|
||||
|
||||
It supports PubSub via the normal mechanisms, including migrating the listeners if the node they are connected to goes down.
|
||||
@@ -60,7 +63,7 @@ createSentinel({
|
||||
});
|
||||
```
|
||||
|
||||
In addition, it also provides the ability have a pool of clients connected to the replica nodes, and to direct all read-only commands to them:
|
||||
In addition, it also provides the ability have a pool of clients connected to the replica nodes, and to direct all read-only commands to them:
|
||||
|
||||
```javascript
|
||||
createSentinel({
|
||||
@@ -85,9 +88,9 @@ const result = await sentinel.use(async client => {
|
||||
});
|
||||
```
|
||||
|
||||
`.getMasterClientLease()`
|
||||
`.acquire()`
|
||||
```javascript
|
||||
const clientLease = await sentinel.getMasterClientLease();
|
||||
const clientLease = await sentinel.acquire();
|
||||
|
||||
try {
|
||||
await clientLease.watch('key');
|
||||
|
@@ -134,7 +134,7 @@ describe(`test with scripts`, () => {
|
||||
}, GLOBAL.SENTINEL.WITH_SCRIPT);
|
||||
|
||||
testUtils.testWithClientSentinel('with script multi', async sentinel => {
|
||||
const reply = await sentinel.multi().set('key', 2).square('key').exec();
|
||||
const reply = await sentinel.multi().set('key', 2).square('key').exec();
|
||||
assert.deepEqual(reply, ['OK', 4]);
|
||||
}, GLOBAL.SENTINEL.WITH_SCRIPT);
|
||||
|
||||
@@ -148,7 +148,7 @@ describe(`test with scripts`, () => {
|
||||
);
|
||||
}, GLOBAL.SENTINEL.WITH_SCRIPT)
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe(`test with functions`, () => {
|
||||
testUtils.testWithClientSentinel('with function', async sentinel => {
|
||||
@@ -178,14 +178,14 @@ describe(`test with functions`, () => {
|
||||
MATH_FUNCTION.code,
|
||||
{ REPLACE: true }
|
||||
);
|
||||
|
||||
|
||||
const reply = await sentinel.use(
|
||||
async (client: any) => {
|
||||
await client.set('key', '2');
|
||||
return client.math.square('key');
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
assert.equal(reply, 4);
|
||||
}, GLOBAL.SENTINEL.WITH_FUNCTION);
|
||||
});
|
||||
@@ -216,7 +216,7 @@ describe(`test with replica pool size 1`, () => {
|
||||
testUtils.testWithClientSentinel('client lease', async sentinel => {
|
||||
sentinel.on("error", () => { });
|
||||
|
||||
const clientLease = await sentinel.aquire();
|
||||
const clientLease = await sentinel.acquire();
|
||||
clientLease.set('x', 456);
|
||||
|
||||
let matched = false;
|
||||
@@ -243,7 +243,7 @@ describe(`test with replica pool size 1`, () => {
|
||||
return await client.get("x");
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
await sentinel.set("x", 1);
|
||||
assert.equal(await promise, null);
|
||||
}, GLOBAL.SENTINEL.WITH_REPLICA_POOL_SIZE_1);
|
||||
@@ -276,7 +276,7 @@ describe(`test with masterPoolSize 2, reserve client true`, () => {
|
||||
assert.equal(await promise2, "2");
|
||||
}, Object.assign(GLOBAL.SENTINEL.WITH_RESERVE_CLIENT_MASTER_POOL_SIZE_2, {skipTest: true}));
|
||||
});
|
||||
|
||||
|
||||
describe(`test with masterPoolSize 2`, () => {
|
||||
testUtils.testWithClientSentinel('multple clients', async sentinel => {
|
||||
sentinel.on("error", () => { });
|
||||
@@ -313,14 +313,14 @@ describe(`test with masterPoolSize 2`, () => {
|
||||
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
|
||||
|
||||
testUtils.testWithClientSentinel('lease - watch - clean', async sentinel => {
|
||||
const leasedClient = await sentinel.aquire();
|
||||
const leasedClient = await sentinel.acquire();
|
||||
await leasedClient.set('x', 1);
|
||||
await leasedClient.watch('x');
|
||||
assert.deepEqual(await leasedClient.multi().get('x').exec(), ['1'])
|
||||
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
|
||||
|
||||
testUtils.testWithClientSentinel('lease - watch - dirty', async sentinel => {
|
||||
const leasedClient = await sentinel.aquire();
|
||||
const leasedClient = await sentinel.acquire();
|
||||
await leasedClient.set('x', 1);
|
||||
await leasedClient.watch('x');
|
||||
await leasedClient.set('x', 2);
|
||||
@@ -328,11 +328,11 @@ describe(`test with masterPoolSize 2`, () => {
|
||||
await assert.rejects(leasedClient.multi().get('x').exec(), new WatchError());
|
||||
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// TODO: Figure out how to modify the test utils
|
||||
// so it would have fine grained controll over
|
||||
// sentinel
|
||||
// sentinel
|
||||
// it should somehow replicate the `SentinelFramework` object functionallities
|
||||
async function steadyState(frame: SentinelFramework) {
|
||||
let checkedMaster = false;
|
||||
@@ -439,7 +439,7 @@ describe.skip('legacy tests', () => {
|
||||
sentinel.on('error', () => { });
|
||||
}
|
||||
|
||||
if (this!.currentTest!.state === 'failed') {
|
||||
if (this!.currentTest!.state === 'failed') {
|
||||
console.log(`longest event loop blocked delta: ${longestDelta}`);
|
||||
console.log(`longest event loop blocked in failing test: ${longestTestDelta}`);
|
||||
console.log("trace:");
|
||||
@@ -454,7 +454,7 @@ describe.skip('legacy tests', () => {
|
||||
frame.sentinelMaster(),
|
||||
frame.sentinelReplicas()
|
||||
])
|
||||
|
||||
|
||||
console.log(`sentinel sentinels:\n${JSON.stringify(results[0], undefined, '\t')}`);
|
||||
console.log(`sentinel master:\n${JSON.stringify(results[1], undefined, '\t')}`);
|
||||
console.log(`sentinel replicas:\n${JSON.stringify(results[2], undefined, '\t')}`);
|
||||
@@ -492,7 +492,7 @@ describe.skip('legacy tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// stops master to force sentinel to update
|
||||
// stops master to force sentinel to update
|
||||
it('stop master', async function () {
|
||||
this.timeout(60000);
|
||||
|
||||
@@ -538,8 +538,8 @@ describe.skip('legacy tests', () => {
|
||||
|
||||
tracer.push("connected");
|
||||
|
||||
const client = await sentinel.aquire();
|
||||
tracer.push("aquired lease");
|
||||
const client = await sentinel.acquire();
|
||||
tracer.push("acquired lease");
|
||||
|
||||
await client.set("x", 1);
|
||||
await client.watch("x");
|
||||
@@ -586,7 +586,7 @@ describe.skip('legacy tests', () => {
|
||||
await sentinel.connect();
|
||||
tracer.push("connected");
|
||||
|
||||
const client = await sentinel.aquire();
|
||||
const client = await sentinel.acquire();
|
||||
tracer.push("got leased client");
|
||||
await client.set("x", 1);
|
||||
await client.watch("x");
|
||||
@@ -965,10 +965,10 @@ describe.skip('legacy tests', () => {
|
||||
tracer.push("adding node");
|
||||
await frame.addNode();
|
||||
tracer.push("added node and waiting on added promise");
|
||||
await nodeAddedPromise;
|
||||
await nodeAddedPromise;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -32,14 +32,30 @@ export class RedisSentinelClient<
|
||||
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
|
||||
readonly _self: RedisSentinelClient<M, F, S, RESP, TYPE_MAPPING>;
|
||||
|
||||
/**
|
||||
* Indicates if the client connection is open
|
||||
*
|
||||
* @returns `true` if the client connection is open, `false` otherwise
|
||||
*/
|
||||
|
||||
get isOpen() {
|
||||
return this._self.#internal.isOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the client connection is ready to accept commands
|
||||
*
|
||||
* @returns `true` if the client connection is ready, `false` otherwise
|
||||
*/
|
||||
get isReady() {
|
||||
return this._self.#internal.isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command options configured for this client
|
||||
*
|
||||
* @returns The command options for this client or `undefined` if none were set
|
||||
*/
|
||||
get commandOptions() {
|
||||
return this._self.#commandOptions;
|
||||
}
|
||||
@@ -222,6 +238,16 @@ export class RedisSentinelClient<
|
||||
|
||||
unwatch = this.UNWATCH;
|
||||
|
||||
/**
|
||||
* Releases the client lease back to the pool
|
||||
*
|
||||
* After calling this method, the client instance should no longer be used as it
|
||||
* will be returned to the client pool and may be given to other operations.
|
||||
*
|
||||
* @returns A promise that resolves when the client is ready to be reused, or undefined
|
||||
* if the client was immediately ready
|
||||
* @throws Error if the lease has already been released
|
||||
*/
|
||||
release() {
|
||||
if (this._self.#clientInfo === undefined) {
|
||||
throw new Error('RedisSentinelClient lease already released');
|
||||
@@ -245,10 +271,20 @@ export default class RedisSentinel<
|
||||
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
|
||||
#options: RedisSentinelOptions<M, F, S, RESP, TYPE_MAPPING>;
|
||||
|
||||
/**
|
||||
* Indicates if the sentinel connection is open
|
||||
*
|
||||
* @returns `true` if the sentinel connection is open, `false` otherwise
|
||||
*/
|
||||
get isOpen() {
|
||||
return this._self.#internal.isOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the sentinel connection is ready to accept commands
|
||||
*
|
||||
* @returns `true` if the sentinel connection is ready, `false` otherwise
|
||||
*/
|
||||
get isReady() {
|
||||
return this._self.#internal.isReady;
|
||||
}
|
||||
@@ -511,7 +547,28 @@ export default class RedisSentinel<
|
||||
|
||||
pUnsubscribe = this.PUNSUBSCRIBE;
|
||||
|
||||
async aquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
|
||||
/**
|
||||
* Acquires a master client lease for exclusive operations
|
||||
*
|
||||
* Used when multiple commands need to run on an exclusive client (for example, using `WATCH/MULTI/EXEC`).
|
||||
* The returned client must be released after use with the `release()` method.
|
||||
*
|
||||
* @returns A promise that resolves to a Redis client connected to the master node
|
||||
* @example
|
||||
* ```javascript
|
||||
* const clientLease = await sentinel.acquire();
|
||||
*
|
||||
* try {
|
||||
* await clientLease.watch('key');
|
||||
* const resp = await clientLease.multi()
|
||||
* .get('key')
|
||||
* .exec();
|
||||
* } finally {
|
||||
* clientLease.release();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async acquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
|
||||
const clientInfo = await this._self.#internal.getClientLease();
|
||||
return RedisSentinelClient.create(this._self.#options, this._self.#internal, clientInfo, this._self.#commandOptions);
|
||||
}
|
||||
@@ -641,6 +698,12 @@ class RedisSentinelInternal<
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a client lease from the master client pool
|
||||
*
|
||||
* @returns A client info object or a promise that resolves to a client info object
|
||||
* when a client becomes available
|
||||
*/
|
||||
getClientLease(): ClientInfo | Promise<ClientInfo> {
|
||||
const id = this.#masterClientQueue.shift();
|
||||
if (id !== undefined) {
|
||||
@@ -650,6 +713,16 @@ class RedisSentinelInternal<
|
||||
return this.#masterClientQueue.wait().then(id => ({ id }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a client lease back to the pool
|
||||
*
|
||||
* If the client was used for a transaction that might have left it in a dirty state,
|
||||
* it will be reset before being returned to the pool.
|
||||
*
|
||||
* @param clientInfo The client info object representing the client to release
|
||||
* @returns A promise that resolves when the client is ready to be reused, or undefined
|
||||
* if the client was immediately ready or no longer exists
|
||||
*/
|
||||
releaseClientLease(clientInfo: ClientInfo) {
|
||||
const client = this.#masterClients[clientInfo.id];
|
||||
// client can be undefined if releasing in middle of a reconfigure
|
||||
|
@@ -49,11 +49,17 @@ export interface RedisSentinelOptions<
|
||||
*/
|
||||
replicaPoolSize?: number;
|
||||
/**
|
||||
* TODO
|
||||
* Interval in milliseconds to periodically scan for changes in the sentinel topology.
|
||||
* The client will query the sentinel for changes at this interval.
|
||||
*
|
||||
* Default: 10000 (10 seconds)
|
||||
*/
|
||||
scanInterval?: number;
|
||||
/**
|
||||
* TODO
|
||||
* When `true`, error events from client instances inside the sentinel will be propagated to the sentinel instance.
|
||||
* This allows handling all client errors through a single error handler on the sentinel instance.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
passthroughClientErrorEvents?: boolean;
|
||||
/**
|
||||
|
Reference in New Issue
Block a user