Skip to content

Sentinel Support #2664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 80 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
59cf984
redis client socket changes needed for sentinel
sjpotter Oct 26, 2023
cd254b1
Sentinel Implementation [EXPERIMENTAL]
sjpotter Nov 13, 2023
18fc971
add pooling
sjpotter Nov 13, 2023
29d4da0
Merge remote-tracking branch 'leibale/v5' into sentinel
sjpotter Nov 15, 2023
27d7f4e
improve typing with SENTINEL_ client members
sjpotter Nov 15, 2023
1b7cfc6
cleanup - remove unused comments / commented code
sjpotter Nov 16, 2023
13a18bd
small sendCommand change + revert change to tsconfig
sjpotter Nov 16, 2023
8f59b35
add more sentinel commands needed for testing.
sjpotter Nov 19, 2023
b7e59d5
lots of fixups and a reasonable first pass test suite
sjpotter Dec 1, 2023
8ae7d1f
add a timer option to update topology in background
sjpotter Dec 3, 2023
d3505ce
format all the things
sjpotter Dec 3, 2023
300889f
more progress
sjpotter Dec 5, 2023
6f14410
Merge remote-tracking branch 'upstream/v5' into sentinel
sjpotter Dec 5, 2023
9e3cda6
small cleanup
sjpotter Dec 5, 2023
18bb2d0
try to group promises together to minimize the internal await points
sjpotter Dec 5, 2023
6bfe7b7
redo events, to keep a single topology event to listen on
sjpotter Dec 6, 2023
5e722ef
nits + readme
sjpotter Dec 6, 2023
f0be3a8
add RedisSentinelFactory to provide lower level access to sentinel
sjpotter Dec 7, 2023
45e64f1
Merge remote-tracking branch 'upstream/v5' into sentinel
sjpotter Dec 7, 2023
1e9e65b
nit
sjpotter Dec 7, 2023
1731059
update
sjpotter Dec 11, 2023
c74c4ba
add RedisSentinelClient/Type for leased clients
sjpotter Dec 12, 2023
c8f74f3
add self for private access + improve emitting
sjpotter Dec 12, 2023
882c1c7
nit
sjpotter Dec 12, 2023
9a59887
nits
sjpotter Dec 12, 2023
84fa90b
improve testing
sjpotter Dec 14, 2023
93cd57a
ismall nit for typing
sjpotter Dec 14, 2023
43a40a1
bunch of changes
sjpotter Dec 15, 2023
57e9889
improve pub sub proxy.
sjpotter Dec 17, 2023
2e6091d
wrap the passed through RedisClient error to make clear where its com…
sjpotter Dec 17, 2023
02bbb13
refactor sentinel object / factory tests apart
sjpotter Dec 17, 2023
53bd858
harden tests a little bit more
sjpotter Dec 18, 2023
a661d89
add pipeline test
sjpotter Dec 18, 2023
6bd1c67
add scripts/function tests + fixups / cleanups to get them to work
sjpotter Dec 18, 2023
173fcc4
change to use redis-stack-server for redis nodes to enable module tes…
sjpotter Dec 19, 2023
c150bfc
fix test, forgot to return in use function with module
sjpotter Dec 19, 2023
65f543e
rename test
sjpotter Dec 19, 2023
5f83c09
improve tests to test with redis/sentinel nodes with and withput pass…
sjpotter Dec 19, 2023
ce35310
cleanup for RedisSentinel type generic typing in tests
sjpotter Dec 19, 2023
0f331dc
remove debugLog, just rely on traace mechanism
sjpotter Dec 19, 2023
9a77d5e
added multi tests for script/function/modules
sjpotter Dec 19, 2023
3da082c
don't emit errors on lease object, only on main object
sjpotter Dec 19, 2023
73e0536
improve testing
sjpotter Dec 20, 2023
89f33f9
extract out common code to reduce duplication
sjpotter Dec 20, 2023
cea648e
nit
sjpotter Dec 20, 2023
8dcd811
nits
sjpotter Dec 20, 2023
60974ca
nit
sjpotter Dec 20, 2023
1f3e19d
remove SENTINEL_... commands from main client, load them via module i…
sjpotter Dec 20, 2023
3dea400
missed adding RedisSentinelModule to correct places in RedisSentinelF…
sjpotter Dec 20, 2023
9ec1838
nits
sjpotter Dec 21, 2023
873014e
fix test logging on error
sjpotter Dec 22, 2023
c567741
invalidate watches when client reconnects
sjpotter Jan 3, 2024
5259124
remove WATCH and UNWATCH command files, fix WATCH and UNWATCH return …
leibale Jan 3, 2024
4f0b040
missing file in last commit :P
leibale Jan 3, 2024
19288ef
support for custom message in `WatchError`
leibale Jan 3, 2024
91b27cd
setDirtyWatch
leibale Jan 3, 2024
7bf97ec
update watch docs
leibale Jan 3, 2024
4567564
Merge branch 'watch-invalidate' into sentinel
sjpotter Jan 8, 2024
8945cf5
fixes needed
sjpotter Jan 8, 2024
c289366
Merge branch 'watch-invalidate' into sentinel
sjpotter Jan 8, 2024
26f72ae
wip
sjpotter Jan 8, 2024
4968e4c
get functions/modules to work again
sjpotter Jan 8, 2024
2554c0c
reuse leased client on pipelined commands.
sjpotter Jan 8, 2024
cc0768b
test tweaks
sjpotter Jan 8, 2024
2b865f1
nit
sjpotter Jan 9, 2024
6959b17
change how "sentinel" object client works, allow it to be reserved
sjpotter Jan 18, 2024
54e8e5e
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 29, 2024
28d6587
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 29, 2024
bb9b466
review
leibale Jan 29, 2024
1920858
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 29, 2024
a3d198e
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 29, 2024
5df6069
fixes to get more tests to pass
sjpotter Jan 29, 2024
838a6f1
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 31, 2024
27cb970
Merge branch 'v5' of github.com:redis/node-redis into sentinel
leibale Jan 31, 2024
bd5a820
handle dirtyWatch and watchEpoch in reset and resetIfDirty
leibale Jan 31, 2024
07e62f3
"fix", but not correct, needs more work
sjpotter Feb 1, 2024
d917967
fix pubsub proxy
leibale Feb 1, 2024
4cb9564
remove timeout from steadyState function in test, caused problems
sjpotter Feb 1, 2024
8da0970
improve restarting nodes
sjpotter Feb 1, 2024
0aefb77
fix pubsub proxy and test
sjpotter Feb 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 120 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@
"tsx": "^4.6.2",
"typedoc": "^0.25.4",
"typescript": "^5.3.2"
},
"dependencies": {
"node-docker-api": "^1.1.22",
"wait-queue": "^1.1.4"
}
}
5 changes: 5 additions & 0 deletions packages/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import RedisCluster, { RedisClusterOptions, RedisClusterType } from './lib/clust
export { RedisClusterType, RedisClusterOptions };
export const createCluster = RedisCluster.create;

import RedisSentinel from './lib/sentinel';
import { RedisSentinelOptions, RedisSentinelType } from './lib/sentinel/types';
export { RedisSentinelType, RedisSentinelOptions };
export const createSentinel = RedisSentinel.create;

// export { GeoReplyWith } from './lib/commands/generic-transformers';

// export { SetOptions } from './lib/commands/SET';
Expand Down
8 changes: 4 additions & 4 deletions packages/client/lib/client/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export interface RedisSocketCommonOptions {
reconnectStrategy?: false | number | ((retries: number, cause: Error) => false | Error | number);
}

type RedisNetSocketOptions = Partial<net.SocketConnectOpts> & {
export interface RedisNetConnectOpts extends Omit<Partial<net.TcpNetConnectOpts>, 'keepAlive'>, Partial<net.IpcNetConnectOpts>, RedisSocketCommonOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this solves #2679, need to check (and if it does, cherry-pick it)

tls?: false;
};

export interface RedisTlsSocketOptions extends tls.ConnectionOptions {
export interface RedisTlsSocketOptions extends Partial<tls.ConnectionOptions>, RedisSocketCommonOptions {
tls: true;
}
};

export type RedisSocketOptions = RedisSocketCommonOptions & (RedisNetSocketOptions | RedisTlsSocketOptions);
export type RedisSocketOptions = RedisNetConnectOpts | RedisTlsSocketOptions

interface CreateSocketReturn<T> {
connectEvent: string;
Expand Down
87 changes: 87 additions & 0 deletions packages/client/lib/sentinel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Redis Sentinel

The Redis Sentinel object of node-redis provides a high level object that provides access to a high availability redis installation managed by Redis Sentinel to provide enumeration of master and replica nodes belonging to an installation as well as reconfigure itself on demand for failover and topology changes.

## Usage

```typescript
//FIXME: are these imports, correct?
import { createSentinel } from '@redis/client';
import { RedisSentinelOptions } from '@redis/client/sentinel/types';

const options: RedisSentinelOptions = {name: "sentinel-db", sentinelRootNodes: [{host: "example", port: 1234}]};
const sentinel = createSentinel(options);
await sentinel.connect();
```

In the above example, we configure the sentinel object to fetch the configuration for the database Redis Sentinel is monitoring as "sentinel-db" with one of the sentinels being located at `exampe:1234`.

Once one has this object, one can use it like a normal Redis client

```typescript
assert(await sentinel.set('x', 1) == 'OK);
const value = await sentinel.get('x');
```

It supports pubsub via the normal mechanisms, including migrating the listeners if the node they are connected to goes down.

```typescript
await sentinel.subscribe('test', (msg) => {});
await sentinel.unsubscribe('test');
```

The sentinel object provides the ability to direct read only commands against replica nodes if configured to do so

```typescript
const options: RedisSentinelOptions = {
name: "sentinel-db",
sentinelRootNodes: [{host: "example", port: 1234}],
useReplicas: true
};
const sentinel = createSentinel(options);
await sentinel.connect();
```

the sentinel object provides the ability to manage a pool of clients for both the master and replicas (if using replica reads)

```typescript
const options: RedisSentinelOptions = {
name: "sentinel-db",
sentinelRootNodes: [{host: "example", port: 1234}],
useReplicas: true,
masterPoolSize: 16,
replicaPoolSize: 4
};
const sentinel = createSentinel(options);
await sentinel.connect();
```

the sentinel object enables the user to get a persistent client lease against the master replicas, if desired. For instance, if one wants to perform redis transactions with `WATCH/MULTI/EXEC`

```typescript
const clientLease = await sentinel.getMasterClientLease();
await clientLease.watch("x");
const resp = await clientLease.multi().get("x").exec();
clientLease.release();
```

If no clients are available to get a lease, the aquistion will block until a client lease is available.

In addition, even without explicit leases, whenever the sentinel object does actions against the master node (ex:

```typescript
await sentinel.set('x', 1)`)
```
it will take a temporary lease on a client, so that it will not step on top of any other clients

In addition, the sentinel object provides the `use()` member to provide the ability to pass in a function that takes a `RedisClientType` and this will aquire a lease for the function's execution and release it upon compeletion

```typescript
let promise = sentinel.use(async (client) => {
await client.set("x", 1);
await client.watch("x");
return client.multi().get("x").exec();
});
```

the usages of `use()` can be non reslient (default) where the sentinel client will not attempt to perform them again if the connections breaks in the middle / a topology reconfiguration is required or in a reslient mode where it will retry it when we reconnect to the master.
12 changes: 12 additions & 0 deletions packages/client/lib/sentinel/commands/SENTINEL_MASTER.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BlobStringReply, Command, MapReply, RedisArgument } from "../../RESP/types";
import { transformTuplesReply } from "../../commands/generic-transformers";

export default {
transformArguments(dbname: RedisArgument) {
return ['SENTINEL', 'MASTER', dbname];
},
transformReply: {
2: transformTuplesReply,
3: undefined as unknown as () => MapReply<BlobStringReply, BlobStringReply>
}
} as const satisfies Command;
8 changes: 8 additions & 0 deletions packages/client/lib/sentinel/commands/SENTINEL_MONITOR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SimpleStringReply, Command, RedisArgument } from "../../RESP/types";

export default {
transformArguments(dbname: RedisArgument, host: RedisArgument, port: RedisArgument, quorum: RedisArgument) {
return ['SENTINEL', 'MONITOR', dbname, host, port, quorum];
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;
Loading