index.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const events_1 = require("events");
  4. const ClusterAllFailedError_1 = require("../errors/ClusterAllFailedError");
  5. const utils_1 = require("../utils");
  6. const ConnectionPool_1 = require("./ConnectionPool");
  7. const util_1 = require("./util");
  8. const ClusterSubscriber_1 = require("./ClusterSubscriber");
  9. const DelayQueue_1 = require("./DelayQueue");
  10. const ScanStream_1 = require("../ScanStream");
  11. const redis_errors_1 = require("redis-errors");
  12. const standard_as_callback_1 = require("standard-as-callback");
  13. const PromiseContainer = require("../promiseContainer");
  14. const ClusterOptions_1 = require("./ClusterOptions");
  15. const utils_2 = require("../utils");
  16. const commands = require("redis-commands");
  17. const command_1 = require("../command");
  18. const redis_1 = require("../redis");
  19. const commander_1 = require("../commander");
  20. const Deque = require("denque");
  21. const debug = utils_1.Debug("cluster");
  22. /**
  23. * Client for the official Redis Cluster
  24. *
  25. * @class Cluster
  26. * @extends {EventEmitter}
  27. */
  28. class Cluster extends events_1.EventEmitter {
  29. /**
  30. * Creates an instance of Cluster.
  31. *
  32. * @param {(Array<string | number | object>)} startupNodes
  33. * @param {IClusterOptions} [options={}]
  34. * @memberof Cluster
  35. */
  36. constructor(startupNodes, options = {}) {
  37. super();
  38. this.slots = [];
  39. this.retryAttempts = 0;
  40. this.delayQueue = new DelayQueue_1.default();
  41. this.offlineQueue = new Deque();
  42. this.isRefreshing = false;
  43. /**
  44. * Every time Cluster#connect() is called, this value will be
  45. * auto-incrementing. The purpose of this value is used for
  46. * discarding previous connect attampts when creating a new
  47. * connection.
  48. *
  49. * @private
  50. * @type {number}
  51. * @memberof Cluster
  52. */
  53. this.connectionEpoch = 0;
  54. commander_1.default.call(this);
  55. this.startupNodes = startupNodes;
  56. this.options = utils_1.defaults({}, options, ClusterOptions_1.DEFAULT_CLUSTER_OPTIONS, this.options);
  57. // validate options
  58. if (typeof this.options.scaleReads !== "function" &&
  59. ["all", "master", "slave"].indexOf(this.options.scaleReads) === -1) {
  60. throw new Error('Invalid option scaleReads "' +
  61. this.options.scaleReads +
  62. '". Expected "all", "master", "slave" or a custom function');
  63. }
  64. this.connectionPool = new ConnectionPool_1.default(this.options.redisOptions);
  65. this.connectionPool.on("-node", (redis, key) => {
  66. this.emit("-node", redis);
  67. });
  68. this.connectionPool.on("+node", redis => {
  69. this.emit("+node", redis);
  70. });
  71. this.connectionPool.on("drain", () => {
  72. this.setStatus("close");
  73. });
  74. this.connectionPool.on("nodeError", (error, key) => {
  75. this.emit("node error", error, key);
  76. });
  77. this.subscriber = new ClusterSubscriber_1.default(this.connectionPool, this);
  78. if (this.options.lazyConnect) {
  79. this.setStatus("wait");
  80. }
  81. else {
  82. this.connect().catch(err => {
  83. debug("connecting failed: %s", err);
  84. });
  85. }
  86. }
  87. resetOfflineQueue() {
  88. this.offlineQueue = new Deque();
  89. }
  90. clearNodesRefreshInterval() {
  91. if (this.slotsTimer) {
  92. clearTimeout(this.slotsTimer);
  93. this.slotsTimer = null;
  94. }
  95. }
  96. resetNodesRefreshInterval() {
  97. if (this.slotsTimer) {
  98. return;
  99. }
  100. const nextRound = () => {
  101. this.slotsTimer = setTimeout(() => {
  102. debug('refreshing slot caches... (triggered by "slotsRefreshInterval" option)');
  103. this.refreshSlotsCache(() => {
  104. nextRound();
  105. });
  106. }, this.options.slotsRefreshInterval);
  107. };
  108. nextRound();
  109. }
  110. /**
  111. * Connect to a cluster
  112. *
  113. * @returns {Promise<void>}
  114. * @memberof Cluster
  115. */
  116. connect() {
  117. const Promise = PromiseContainer.get();
  118. return new Promise((resolve, reject) => {
  119. if (this.status === "connecting" ||
  120. this.status === "connect" ||
  121. this.status === "ready") {
  122. reject(new Error("Redis is already connecting/connected"));
  123. return;
  124. }
  125. const epoch = ++this.connectionEpoch;
  126. this.setStatus("connecting");
  127. this.resolveStartupNodeHostnames()
  128. .then(nodes => {
  129. if (this.connectionEpoch !== epoch) {
  130. debug("discard connecting after resolving startup nodes because epoch not match: %d != %d", epoch, this.connectionEpoch);
  131. reject(new redis_errors_1.RedisError("Connection is discarded because a new connection is made"));
  132. return;
  133. }
  134. if (this.status !== "connecting") {
  135. debug("discard connecting after resolving startup nodes because the status changed to %s", this.status);
  136. reject(new redis_errors_1.RedisError("Connection is aborted"));
  137. return;
  138. }
  139. this.connectionPool.reset(nodes);
  140. function readyHandler() {
  141. this.setStatus("ready");
  142. this.retryAttempts = 0;
  143. this.executeOfflineCommands();
  144. this.resetNodesRefreshInterval();
  145. resolve();
  146. }
  147. let closeListener;
  148. const refreshListener = () => {
  149. this.removeListener("close", closeListener);
  150. this.manuallyClosing = false;
  151. this.setStatus("connect");
  152. if (this.options.enableReadyCheck) {
  153. this.readyCheck((err, fail) => {
  154. if (err || fail) {
  155. debug("Ready check failed (%s). Reconnecting...", err || fail);
  156. if (this.status === "connect") {
  157. this.disconnect(true);
  158. }
  159. }
  160. else {
  161. readyHandler.call(this);
  162. }
  163. });
  164. }
  165. else {
  166. readyHandler.call(this);
  167. }
  168. };
  169. closeListener = function () {
  170. this.removeListener("refresh", refreshListener);
  171. reject(new Error("None of startup nodes is available"));
  172. };
  173. this.once("refresh", refreshListener);
  174. this.once("close", closeListener);
  175. this.once("close", this.handleCloseEvent.bind(this));
  176. this.refreshSlotsCache(function (err) {
  177. if (err && err.message === "Failed to refresh slots cache.") {
  178. redis_1.default.prototype.silentEmit.call(this, "error", err);
  179. this.connectionPool.reset([]);
  180. }
  181. }.bind(this));
  182. this.subscriber.start();
  183. })
  184. .catch(err => {
  185. this.setStatus("close");
  186. this.handleCloseEvent(err);
  187. reject(err);
  188. });
  189. });
  190. }
  191. /**
  192. * Called when closed to check whether a reconnection should be made
  193. *
  194. * @private
  195. * @memberof Cluster
  196. */
  197. handleCloseEvent(reason) {
  198. if (reason) {
  199. debug("closed because %s", reason);
  200. }
  201. let retryDelay;
  202. if (!this.manuallyClosing &&
  203. typeof this.options.clusterRetryStrategy === "function") {
  204. retryDelay = this.options.clusterRetryStrategy.call(this, ++this.retryAttempts, reason);
  205. }
  206. if (typeof retryDelay === "number") {
  207. this.setStatus("reconnecting");
  208. this.reconnectTimeout = setTimeout(function () {
  209. this.reconnectTimeout = null;
  210. debug("Cluster is disconnected. Retrying after %dms", retryDelay);
  211. this.connect().catch(function (err) {
  212. debug("Got error %s when reconnecting. Ignoring...", err);
  213. });
  214. }.bind(this), retryDelay);
  215. }
  216. else {
  217. this.setStatus("end");
  218. this.flushQueue(new Error("None of startup nodes is available"));
  219. }
  220. }
  221. /**
  222. * Disconnect from every node in the cluster.
  223. *
  224. * @param {boolean} [reconnect=false]
  225. * @memberof Cluster
  226. */
  227. disconnect(reconnect = false) {
  228. const status = this.status;
  229. this.setStatus("disconnecting");
  230. if (!reconnect) {
  231. this.manuallyClosing = true;
  232. }
  233. if (this.reconnectTimeout) {
  234. clearTimeout(this.reconnectTimeout);
  235. this.reconnectTimeout = null;
  236. debug("Canceled reconnecting attempts");
  237. }
  238. this.clearNodesRefreshInterval();
  239. this.subscriber.stop();
  240. if (status === "wait") {
  241. this.setStatus("close");
  242. this.handleCloseEvent();
  243. }
  244. else {
  245. this.connectionPool.reset([]);
  246. }
  247. }
  248. /**
  249. * Quit the cluster gracefully.
  250. *
  251. * @param {CallbackFunction<'OK'>} [callback]
  252. * @returns {Promise<'OK'>}
  253. * @memberof Cluster
  254. */
  255. quit(callback) {
  256. const status = this.status;
  257. this.setStatus("disconnecting");
  258. this.manuallyClosing = true;
  259. if (this.reconnectTimeout) {
  260. clearTimeout(this.reconnectTimeout);
  261. this.reconnectTimeout = null;
  262. }
  263. this.clearNodesRefreshInterval();
  264. this.subscriber.stop();
  265. const Promise = PromiseContainer.get();
  266. if (status === "wait") {
  267. const ret = standard_as_callback_1.default(Promise.resolve("OK"), callback);
  268. // use setImmediate to make sure "close" event
  269. // being emitted after quit() is returned
  270. setImmediate(function () {
  271. this.setStatus("close");
  272. this.handleCloseEvent();
  273. }.bind(this));
  274. return ret;
  275. }
  276. return standard_as_callback_1.default(Promise.all(this.nodes().map(node => node.quit().catch(err => {
  277. // Ignore the error caused by disconnecting since
  278. // we're disconnecting...
  279. if (err.message === utils_2.CONNECTION_CLOSED_ERROR_MSG) {
  280. return "OK";
  281. }
  282. throw err;
  283. }))).then(() => "OK"), callback);
  284. }
  285. /**
  286. * Create a new instance with the same startup nodes and options as the current one.
  287. *
  288. * @example
  289. * ```js
  290. * var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
  291. * var anotherCluster = cluster.duplicate();
  292. * ```
  293. *
  294. * @public
  295. * @param {(Array<string | number | object>)} [overrideStartupNodes=[]]
  296. * @param {IClusterOptions} [overrideOptions={}]
  297. * @memberof Cluster
  298. */
  299. duplicate(overrideStartupNodes = [], overrideOptions = {}) {
  300. const startupNodes = overrideStartupNodes.length > 0
  301. ? overrideStartupNodes
  302. : this.startupNodes.slice(0);
  303. const options = Object.assign({}, this.options, overrideOptions);
  304. return new Cluster(startupNodes, options);
  305. }
  306. /**
  307. * Get nodes with the specified role
  308. *
  309. * @param {NodeRole} [role='all']
  310. * @returns {any[]}
  311. * @memberof Cluster
  312. */
  313. nodes(role = "all") {
  314. if (role !== "all" && role !== "master" && role !== "slave") {
  315. throw new Error('Invalid role "' + role + '". Expected "all", "master" or "slave"');
  316. }
  317. return this.connectionPool.getNodes(role);
  318. }
  319. /**
  320. * Change cluster instance's status
  321. *
  322. * @private
  323. * @param {ClusterStatus} status
  324. * @memberof Cluster
  325. */
  326. setStatus(status) {
  327. debug("status: %s -> %s", this.status || "[empty]", status);
  328. this.status = status;
  329. process.nextTick(() => {
  330. this.emit(status);
  331. });
  332. }
  333. /**
  334. * Refresh the slot cache
  335. *
  336. * @private
  337. * @param {CallbackFunction} [callback]
  338. * @memberof Cluster
  339. */
  340. refreshSlotsCache(callback) {
  341. if (this.isRefreshing) {
  342. if (typeof callback === "function") {
  343. process.nextTick(callback);
  344. }
  345. return;
  346. }
  347. this.isRefreshing = true;
  348. const _this = this;
  349. const wrapper = function (error) {
  350. _this.isRefreshing = false;
  351. if (typeof callback === "function") {
  352. callback(error);
  353. }
  354. };
  355. const nodes = utils_2.shuffle(this.connectionPool.getNodes());
  356. let lastNodeError = null;
  357. function tryNode(index) {
  358. if (index === nodes.length) {
  359. const error = new ClusterAllFailedError_1.default("Failed to refresh slots cache.", lastNodeError);
  360. return wrapper(error);
  361. }
  362. const node = nodes[index];
  363. const key = `${node.options.host}:${node.options.port}`;
  364. debug("getting slot cache from %s", key);
  365. _this.getInfoFromNode(node, function (err) {
  366. switch (_this.status) {
  367. case "close":
  368. case "end":
  369. return wrapper(new Error("Cluster is disconnected."));
  370. case "disconnecting":
  371. return wrapper(new Error("Cluster is disconnecting."));
  372. }
  373. if (err) {
  374. _this.emit("node error", err, key);
  375. lastNodeError = err;
  376. tryNode(index + 1);
  377. }
  378. else {
  379. _this.emit("refresh");
  380. wrapper();
  381. }
  382. });
  383. }
  384. tryNode(0);
  385. }
  386. /**
  387. * Flush offline queue with error.
  388. *
  389. * @param {Error} error
  390. * @memberof Cluster
  391. */
  392. flushQueue(error) {
  393. let item;
  394. while (this.offlineQueue.length > 0) {
  395. item = this.offlineQueue.shift();
  396. item.command.reject(error);
  397. }
  398. }
  399. executeOfflineCommands() {
  400. if (this.offlineQueue.length) {
  401. debug("send %d commands in offline queue", this.offlineQueue.length);
  402. const offlineQueue = this.offlineQueue;
  403. this.resetOfflineQueue();
  404. while (offlineQueue.length > 0) {
  405. const item = offlineQueue.shift();
  406. this.sendCommand(item.command, item.stream, item.node);
  407. }
  408. }
  409. }
  410. natMapper(nodeKey) {
  411. if (this.options.natMap && typeof this.options.natMap === "object") {
  412. const key = typeof nodeKey === "string"
  413. ? nodeKey
  414. : `${nodeKey.host}:${nodeKey.port}`;
  415. const mapped = this.options.natMap[key];
  416. if (mapped) {
  417. debug("NAT mapping %s -> %O", key, mapped);
  418. return Object.assign({}, mapped);
  419. }
  420. }
  421. return typeof nodeKey === "string"
  422. ? util_1.nodeKeyToRedisOptions(nodeKey)
  423. : nodeKey;
  424. }
  425. sendCommand(command, stream, node) {
  426. if (this.status === "wait") {
  427. this.connect().catch(utils_1.noop);
  428. }
  429. if (this.status === "end") {
  430. command.reject(new Error(utils_2.CONNECTION_CLOSED_ERROR_MSG));
  431. return command.promise;
  432. }
  433. let to = this.options.scaleReads;
  434. if (to !== "master") {
  435. const isCommandReadOnly = commands.exists(command.name) &&
  436. commands.hasFlag(command.name, "readonly");
  437. if (!isCommandReadOnly) {
  438. to = "master";
  439. }
  440. }
  441. let targetSlot = node ? node.slot : command.getSlot();
  442. const ttl = {};
  443. const _this = this;
  444. if (!node && !command.__is_reject_overwritten) {
  445. // eslint-disable-next-line @typescript-eslint/camelcase
  446. command.__is_reject_overwritten = true;
  447. const reject = command.reject;
  448. command.reject = function (err) {
  449. const partialTry = tryConnection.bind(null, true);
  450. _this.handleError(err, ttl, {
  451. moved: function (slot, key) {
  452. debug("command %s is moved to %s", command.name, key);
  453. targetSlot = Number(slot);
  454. if (_this.slots[slot]) {
  455. _this.slots[slot][0] = key;
  456. }
  457. else {
  458. _this.slots[slot] = [key];
  459. }
  460. _this.connectionPool.findOrCreate(_this.natMapper(key));
  461. tryConnection();
  462. debug("refreshing slot caches... (triggered by MOVED error)");
  463. _this.refreshSlotsCache();
  464. },
  465. ask: function (slot, key) {
  466. debug("command %s is required to ask %s:%s", command.name, key);
  467. const mapped = _this.natMapper(key);
  468. _this.connectionPool.findOrCreate(mapped);
  469. tryConnection(false, `${mapped.host}:${mapped.port}`);
  470. },
  471. tryagain: partialTry,
  472. clusterDown: partialTry,
  473. connectionClosed: partialTry,
  474. maxRedirections: function (redirectionError) {
  475. reject.call(command, redirectionError);
  476. },
  477. defaults: function () {
  478. reject.call(command, err);
  479. }
  480. });
  481. };
  482. }
  483. tryConnection();
  484. function tryConnection(random, asking) {
  485. if (_this.status === "end") {
  486. command.reject(new redis_errors_1.AbortError("Cluster is ended."));
  487. return;
  488. }
  489. let redis;
  490. if (_this.status === "ready" || command.name === "cluster") {
  491. if (node && node.redis) {
  492. redis = node.redis;
  493. }
  494. else if (command_1.default.checkFlag("ENTER_SUBSCRIBER_MODE", command.name) ||
  495. command_1.default.checkFlag("EXIT_SUBSCRIBER_MODE", command.name)) {
  496. redis = _this.subscriber.getInstance();
  497. if (!redis) {
  498. command.reject(new redis_errors_1.AbortError("No subscriber for the cluster"));
  499. return;
  500. }
  501. }
  502. else {
  503. if (!random) {
  504. if (typeof targetSlot === "number" && _this.slots[targetSlot]) {
  505. const nodeKeys = _this.slots[targetSlot];
  506. if (typeof to === "function") {
  507. const nodes = nodeKeys.map(function (key) {
  508. return _this.connectionPool.getInstanceByKey(key);
  509. });
  510. redis = to(nodes, command);
  511. if (Array.isArray(redis)) {
  512. redis = utils_2.sample(redis);
  513. }
  514. if (!redis) {
  515. redis = nodes[0];
  516. }
  517. }
  518. else {
  519. let key;
  520. if (to === "all") {
  521. key = utils_2.sample(nodeKeys);
  522. }
  523. else if (to === "slave" && nodeKeys.length > 1) {
  524. key = utils_2.sample(nodeKeys, 1);
  525. }
  526. else {
  527. key = nodeKeys[0];
  528. }
  529. redis = _this.connectionPool.getInstanceByKey(key);
  530. }
  531. }
  532. if (asking) {
  533. redis = _this.connectionPool.getInstanceByKey(asking);
  534. redis.asking();
  535. }
  536. }
  537. if (!redis) {
  538. redis =
  539. (typeof to === "function"
  540. ? null
  541. : _this.connectionPool.getSampleInstance(to)) ||
  542. _this.connectionPool.getSampleInstance("all");
  543. }
  544. }
  545. if (node && !node.redis) {
  546. node.redis = redis;
  547. }
  548. }
  549. if (redis) {
  550. redis.sendCommand(command, stream);
  551. }
  552. else if (_this.options.enableOfflineQueue) {
  553. _this.offlineQueue.push({
  554. command: command,
  555. stream: stream,
  556. node: node
  557. });
  558. }
  559. else {
  560. command.reject(new Error("Cluster isn't ready and enableOfflineQueue options is false"));
  561. }
  562. }
  563. return command.promise;
  564. }
  565. handleError(error, ttl, handlers) {
  566. if (typeof ttl.value === "undefined") {
  567. ttl.value = this.options.maxRedirections;
  568. }
  569. else {
  570. ttl.value -= 1;
  571. }
  572. if (ttl.value <= 0) {
  573. handlers.maxRedirections(new Error("Too many Cluster redirections. Last error: " + error));
  574. return;
  575. }
  576. const errv = error.message.split(" ");
  577. if (errv[0] === "MOVED" || errv[0] === "ASK") {
  578. handlers[errv[0] === "MOVED" ? "moved" : "ask"](errv[1], errv[2]);
  579. }
  580. else if (errv[0] === "TRYAGAIN") {
  581. this.delayQueue.push("tryagain", handlers.tryagain, {
  582. timeout: this.options.retryDelayOnTryAgain
  583. });
  584. }
  585. else if (errv[0] === "CLUSTERDOWN" &&
  586. this.options.retryDelayOnClusterDown > 0) {
  587. this.delayQueue.push("clusterdown", handlers.connectionClosed, {
  588. timeout: this.options.retryDelayOnClusterDown,
  589. callback: this.refreshSlotsCache.bind(this)
  590. });
  591. }
  592. else if (error.message === utils_2.CONNECTION_CLOSED_ERROR_MSG &&
  593. this.options.retryDelayOnFailover > 0 &&
  594. this.status === "ready") {
  595. this.delayQueue.push("failover", handlers.connectionClosed, {
  596. timeout: this.options.retryDelayOnFailover,
  597. callback: this.refreshSlotsCache.bind(this)
  598. });
  599. }
  600. else {
  601. handlers.defaults();
  602. }
  603. }
  604. getInfoFromNode(redis, callback) {
  605. if (!redis) {
  606. return callback(new Error("Node is disconnected"));
  607. }
  608. // Use a duplication of the connection to avoid
  609. // timeouts when the connection is in the blocking
  610. // mode (e.g. waiting for BLPOP).
  611. const duplicatedConnection = redis.duplicate({
  612. enableOfflineQueue: true,
  613. enableReadyCheck: false,
  614. retryStrategy: null,
  615. connectionName: "ioredisClusterRefresher"
  616. });
  617. // Ignore error events since we will handle
  618. // exceptions for the CLUSTER SLOTS command.
  619. duplicatedConnection.on("error", utils_1.noop);
  620. duplicatedConnection.cluster("slots", utils_2.timeout((err, result) => {
  621. duplicatedConnection.disconnect();
  622. if (err) {
  623. return callback(err);
  624. }
  625. if (this.status === "disconnecting" ||
  626. this.status === "close" ||
  627. this.status === "end") {
  628. debug("ignore CLUSTER.SLOTS results (count: %d) since cluster status is %s", result.length, this.status);
  629. callback();
  630. return;
  631. }
  632. const nodes = [];
  633. debug("cluster slots result count: %d", result.length);
  634. for (let i = 0; i < result.length; ++i) {
  635. const items = result[i];
  636. const slotRangeStart = items[0];
  637. const slotRangeEnd = items[1];
  638. const keys = [];
  639. for (let j = 2; j < items.length; j++) {
  640. items[j] = this.natMapper({ host: items[j][0], port: items[j][1] });
  641. items[j].readOnly = j !== 2;
  642. nodes.push(items[j]);
  643. keys.push(items[j].host + ":" + items[j].port);
  644. }
  645. debug("cluster slots result [%d]: slots %d~%d served by %s", i, slotRangeStart, slotRangeEnd, keys);
  646. for (let slot = slotRangeStart; slot <= slotRangeEnd; slot++) {
  647. this.slots[slot] = keys;
  648. }
  649. }
  650. this.connectionPool.reset(nodes);
  651. callback();
  652. }, this.options.slotsRefreshTimeout));
  653. }
  654. /**
  655. * Check whether Cluster is able to process commands
  656. *
  657. * @param {Function} callback
  658. * @private
  659. */
  660. readyCheck(callback) {
  661. this.cluster("info", function (err, res) {
  662. if (err) {
  663. return callback(err);
  664. }
  665. if (typeof res !== "string") {
  666. return callback();
  667. }
  668. let state;
  669. const lines = res.split("\r\n");
  670. for (let i = 0; i < lines.length; ++i) {
  671. const parts = lines[i].split(":");
  672. if (parts[0] === "cluster_state") {
  673. state = parts[1];
  674. break;
  675. }
  676. }
  677. if (state === "fail") {
  678. debug("cluster state not ok (%s)", state);
  679. callback(null, state);
  680. }
  681. else {
  682. callback();
  683. }
  684. });
  685. }
  686. dnsLookup(hostname) {
  687. return new Promise((resolve, reject) => {
  688. this.options.dnsLookup(hostname, (err, address) => {
  689. if (err) {
  690. debug("failed to resolve hostname %s to IP: %s", hostname, err.message);
  691. reject(err);
  692. }
  693. else {
  694. debug("resolved hostname %s to IP %s", hostname, address);
  695. resolve(address);
  696. }
  697. });
  698. });
  699. }
  700. /**
  701. * Normalize startup nodes, and resolving hostnames to IPs.
  702. *
  703. * This process happens every time when #connect() is called since
  704. * #startupNodes and DNS records may chanage.
  705. *
  706. * @private
  707. * @returns {Promise<IRedisOptions[]>}
  708. */
  709. resolveStartupNodeHostnames() {
  710. if (!Array.isArray(this.startupNodes) || this.startupNodes.length === 0) {
  711. return Promise.reject(new Error("`startupNodes` should contain at least one node."));
  712. }
  713. const startupNodes = util_1.normalizeNodeOptions(this.startupNodes);
  714. const hostnames = util_1.getUniqueHostnamesFromOptions(startupNodes);
  715. if (hostnames.length === 0) {
  716. return Promise.resolve(startupNodes);
  717. }
  718. return Promise.all(hostnames.map(hostname => this.dnsLookup(hostname))).then(ips => {
  719. const hostnameToIP = utils_2.zipMap(hostnames, ips);
  720. return startupNodes.map(node => hostnameToIP.has(node.host)
  721. ? Object.assign({}, node, { host: hostnameToIP.get(node.host) })
  722. : node);
  723. });
  724. }
  725. }
  726. Object.getOwnPropertyNames(commander_1.default.prototype).forEach(name => {
  727. if (!Cluster.prototype.hasOwnProperty(name)) {
  728. Cluster.prototype[name] = commander_1.default.prototype[name];
  729. }
  730. });
  731. const scanCommands = [
  732. "sscan",
  733. "hscan",
  734. "zscan",
  735. "sscanBuffer",
  736. "hscanBuffer",
  737. "zscanBuffer"
  738. ];
  739. scanCommands.forEach(command => {
  740. Cluster.prototype[command + "Stream"] = function (key, options) {
  741. return new ScanStream_1.default(utils_1.defaults({
  742. objectMode: true,
  743. key: key,
  744. redis: this,
  745. command: command
  746. }, options));
  747. };
  748. });
  749. require("../transaction").addTransactionSupport(Cluster.prototype);
  750. exports.default = Cluster;