Skip to content

Commit 16643e2

Browse files
authoredOct 14, 2018
refactor: migrate cluster-related code to TypeScript (#717)
1 parent 80f4a45 commit 16643e2

15 files changed

+848
-699
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ node_modules
22
*.cpuprofile
33
/test.js
44
built
5+
6+
.vscode

‎lib/cluster/ClusterOptions.ts

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {NodeRole} from './util'
2+
3+
/**
4+
* Options for Cluster constructor
5+
*
6+
* @export
7+
* @interface IClusterOptions
8+
*/
9+
export interface IClusterOptions {
10+
/**
11+
* See "Quick Start" section.
12+
*
13+
* @default (times) => Math.min(100 + times * 2, 2000)
14+
*/
15+
clusterRetryStrategy?: (times: number) => number | null
16+
17+
/**
18+
* See Redis class.
19+
*
20+
* @default true
21+
*/
22+
enableOfflineQueue?: boolean
23+
24+
/**
25+
* When enabled, ioredis only emits "ready" event when `CLUSTER INFO`
26+
* command reporting the cluster is ready for handling commands.
27+
*
28+
* @default true
29+
*/
30+
enableReadyCheck?: boolean
31+
32+
/**
33+
* Scale reads to the node with the specified role.
34+
*
35+
* @default "master"
36+
*/
37+
scaleReads?: NodeRole | Function
38+
39+
/**
40+
* When a MOVED or ASK error is received, client will redirect the
41+
* command to another node.
42+
* This option limits the max redirections allowed to send a command.
43+
*
44+
* @default 16
45+
*/
46+
maxRedirections?: number
47+
48+
/**
49+
* When an error is received when sending a command (e.g.
50+
* "Connection is closed." when the target Redis node is down), client will retry
51+
* if `retryDelayOnFailover` is valid delay time (in ms).
52+
*
53+
* @default 100
54+
*/
55+
retryDelayOnFailover?: number
56+
57+
/**
58+
* When a CLUSTERDOWN error is received, client will retry
59+
* if `retryDelayOnClusterDown` is valid delay time (in ms).
60+
*
61+
* @default 100
62+
*/
63+
retryDelayOnClusterDown?: number
64+
65+
/**
66+
* When a TRYAGAIN error is received, client will retry
67+
* if `retryDelayOnTryAgain` is valid delay time (in ms).
68+
*
69+
* @default 100
70+
*/
71+
retryDelayOnTryAgain?: number
72+
73+
/**
74+
* The milliseconds before a timeout occurs while refreshing
75+
* slots from the cluster.
76+
*
77+
* @default 1000
78+
*/
79+
slotsRefreshTimeout?: number
80+
81+
/**
82+
* The milliseconds between every automatic slots refresh.
83+
*
84+
* @default 5000
85+
*/
86+
slotsRefreshInterval?: number
87+
88+
/**
89+
* Passed to the constructor of `Redis`
90+
*
91+
* @default null
92+
*/
93+
redisOptions?: any
94+
95+
/**
96+
* @default false
97+
*/
98+
lazyConnect?: boolean
99+
}
100+
101+
export const DEFAULT_CLUSTER_OPTIONS: IClusterOptions = {
102+
clusterRetryStrategy: (times) => Math.min(100 + times * 2, 2000),
103+
enableOfflineQueue: true,
104+
enableReadyCheck: true,
105+
scaleReads: 'master',
106+
maxRedirections: 16,
107+
retryDelayOnFailover: 100,
108+
retryDelayOnClusterDown: 100,
109+
retryDelayOnTryAgain: 100,
110+
slotsRefreshTimeout: 1000,
111+
slotsRefreshInterval: 5000
112+
}

‎lib/cluster/ConnectionPool.ts

+16-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {parseURL} from '../utils'
21
import {EventEmitter} from 'events'
2+
import {sample} from '../utils'
33
import {noop, defaults} from '../utils/lodash'
4-
import {IRedisOptions, getNodeKey} from './util'
4+
import {IRedisOptions, getNodeKey, NodeKey, NodeRole} from './util'
55

66
const Redis = require('../redis')
77
const debug = require('../utils/debug')('ioredis:cluster:connectionPool')
@@ -22,11 +22,21 @@ export default class ConnectionPool extends EventEmitter {
2222
super()
2323
}
2424

25-
public getNodes(role: 'all' | 'master' | 'slave' = 'all'): any[] {
25+
public getNodes(role: NodeRole = 'all'): any[] {
2626
const nodes = this.nodes[role]
2727
return Object.keys(nodes).map((key) => nodes[key])
2828
}
2929

30+
public getInstanceByKey(key: NodeKey): any {
31+
return this.nodes.all[key]
32+
}
33+
34+
public getSampleInstance(role: NodeRole): any {
35+
const keys = Object.keys(this.nodes[role])
36+
const sampleKey = sample(keys)
37+
return this.nodes[role][sampleKey]
38+
}
39+
3040
/**
3141
* Find or create a connection to the node
3242
*
@@ -36,7 +46,6 @@ export default class ConnectionPool extends EventEmitter {
3646
* @memberof ConnectionPool
3747
*/
3848
public findOrCreate(node: IRedisOptions, readOnly: boolean = false): any {
39-
fillDefaultOptions(node)
4049
const key = getNodeKey(node)
4150
readOnly = Boolean(readOnly)
4251

@@ -104,28 +113,12 @@ export default class ConnectionPool extends EventEmitter {
104113
* @param {(Array<string | number | object>)} nodes
105114
* @memberof ConnectionPool
106115
*/
107-
public reset(nodes: Array<string | number | object>): void {
116+
public reset(nodes: IRedisOptions[]): void {
108117
debug('Reset with %O', nodes);
109118
const newNodes = {}
110119
nodes.forEach((node) => {
111-
const options: IRedisOptions = {}
112-
if (typeof node === 'object') {
113-
Object.assign(options, node)
114-
} else if (typeof node === 'string') {
115-
Object.assign(options, parseURL(node))
116-
} else if (typeof node === 'number') {
117-
options.port = node
118-
} else {
119-
throw new Error('Invalid argument ' + node)
120-
}
121-
if (typeof options.port === 'string') {
122-
options.port = parseInt(options.port, 10)
123-
}
124-
delete options.db
125-
126-
fillDefaultOptions(options)
127-
newNodes[getNodeKey(options)] = options
128-
}, this)
120+
newNodes[getNodeKey(node)] = node
121+
})
129122

130123
Object.keys(this.nodes.all).forEach((key) => {
131124
if (!newNodes[key]) {
@@ -139,8 +132,3 @@ export default class ConnectionPool extends EventEmitter {
139132
})
140133
}
141134
}
142-
143-
function fillDefaultOptions(node: IRedisOptions): void {
144-
node.port = node.port || 6379
145-
node.host = node.host || '127.0.0.1'
146-
}

‎lib/cluster/index.js

-659
This file was deleted.

‎lib/cluster/index.ts

+648
Large diffs are not rendered by default.

‎lib/cluster/util.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
1+
import {parseURL} from '../utils'
2+
3+
export type NodeKey = string
4+
export type NodeRole = 'master' | 'slave' | 'all'
5+
16
export interface IRedisOptions {
2-
[key: string]: any
7+
port: number,
8+
host: string
39
}
410

5-
export function getNodeKey(node: IRedisOptions): string {
11+
export function getNodeKey(node: IRedisOptions): NodeKey {
612
node.port = node.port || 6379
713
node.host = node.host || '127.0.0.1'
814
return node.host + ':' + node.port
915
}
16+
17+
export function normalizeNodeOptions(nodes: Array<string | number | object>): IRedisOptions[] {
18+
return nodes.map((node) => {
19+
const options: any = {}
20+
if (typeof node === 'object') {
21+
Object.assign(options, node)
22+
} else if (typeof node === 'string') {
23+
Object.assign(options, parseURL(node))
24+
} else if (typeof node === 'number') {
25+
options.port = node
26+
} else {
27+
throw new Error('Invalid argument ' + node)
28+
}
29+
if (typeof options.port === 'string') {
30+
options.port = parseInt(options.port, 10)
31+
}
32+
33+
// Cluster mode only support db 0
34+
delete options.db
35+
36+
if (!options.port) {
37+
options.port = 6379
38+
}
39+
if (!options.host) {
40+
options.host = '127.0.0.1'
41+
}
42+
43+
return options
44+
})
45+
}

‎lib/errors/ClusterAllFailedError.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {RedisError} from 'redis-errors'
2+
3+
export default class ClusterAllFailedError extends RedisError {
4+
constructor (message, public lastNodeError: RedisError) {
5+
super(message)
6+
Error.captureStackTrace(this, this.constructor)
7+
}
8+
9+
get name (): string {
10+
return this.constructor.name
11+
}
12+
}

‎lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports = module.exports = require('./redis');
44

55
exports.ReplyError = require('redis-errors').ReplyError;
6-
exports.Cluster = require('./cluster');
6+
exports.Cluster = require('./cluster').default;
77
exports.Command = require('./command');
88
exports.ScanStream = require('./ScanStream').default;
99
exports.Pipeline = require('./pipeline');

‎lib/promiseContainer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ export function isPromise (obj: any): boolean {
44
typeof obj.then === 'function'
55
}
66

7-
let promise = global.Promise
7+
let promise = Promise
88

9-
export function get (): Function {
9+
export function get (): PromiseConstructor {
1010
return promise
1111
}
1212

@@ -15,5 +15,5 @@ export function set (lib: Function): void {
1515
throw new Error(`Provided Promise must be a function, got ${lib}`)
1616
}
1717

18-
promise = lib
18+
promise = lib as PromiseConstructor
1919
}

‎lib/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type CallbackFunction<T = void> = (err?: Error | null, result?: T) => void

‎package-lock.json

+9-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"standard-as-callback": "^1.0.0"
4141
},
4242
"devDependencies": {
43-
"@types/node": "^10.5.2",
43+
"@types/node": "^10.11.5",
44+
"@types/redis-errors": "1.2.0",
4445
"bluebird": "^3.5.1",
4546
"chai": "^3.5.0",
4647
"cz-conventional-changelog": "^2.0.0",

‎test/functional/exports.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('exports', function () {
88

99
describe('.Cluster', function () {
1010
it('should be `Cluster`', function () {
11-
var Cluster = require('../../lib/cluster');
11+
var Cluster = require('../../lib/cluster').default;
1212
expect(Redis.Cluster).to.eql(Cluster);
1313
});
1414
});

‎test/unit/cluster.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var Cluster = require('../../lib/cluster');
3+
var Cluster = require('../../lib/cluster').default;
44

55
describe('cluster', function () {
66
beforeEach(function () {

‎tsconfig.json

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"outDir": "./built",
44
"allowJs": true,
55
"target": "es6",
6+
"lib": ["es6"],
67
"moduleResolution": "node",
78
"types": ["node"],
9+
"typeRoots": ["node_modules/@types"],
810
"module": "commonjs"
911
},
1012
"include": [

0 commit comments

Comments
 (0)
Please sign in to comment.