ClusterSubscriber.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const util_1 = require("./util");
  4. const utils_1 = require("../utils");
  5. const redis_1 = require("../redis");
  6. const debug = utils_1.Debug("cluster:subscriber");
  7. const SUBSCRIBER_CONNECTION_NAME = "ioredisClusterSubscriber";
  8. class ClusterSubscriber {
  9. constructor(connectionPool, emitter) {
  10. this.connectionPool = connectionPool;
  11. this.emitter = emitter;
  12. this.started = false;
  13. this.subscriber = null;
  14. this.connectionPool.on("-node", (_, key) => {
  15. if (!this.started || !this.subscriber) {
  16. return;
  17. }
  18. if (util_1.getNodeKey(this.subscriber.options) === key) {
  19. debug("subscriber has left, selecting a new one...");
  20. this.selectSubscriber();
  21. }
  22. });
  23. this.connectionPool.on("+node", () => {
  24. if (!this.started || this.subscriber) {
  25. return;
  26. }
  27. debug("a new node is discovered and there is no subscriber, selecting a new one...");
  28. this.selectSubscriber();
  29. });
  30. }
  31. getInstance() {
  32. return this.subscriber;
  33. }
  34. selectSubscriber() {
  35. const lastActiveSubscriber = this.lastActiveSubscriber;
  36. // Disconnect the previous subscriber even if there
  37. // will not be a new one.
  38. if (lastActiveSubscriber) {
  39. lastActiveSubscriber.disconnect();
  40. }
  41. const sampleNode = utils_1.sample(this.connectionPool.getNodes());
  42. if (!sampleNode) {
  43. debug("selecting subscriber failed since there is no node discovered in the cluster yet");
  44. this.subscriber = null;
  45. return;
  46. }
  47. const { options } = sampleNode;
  48. debug("selected a subscriber %s:%s", options.host, options.port);
  49. /*
  50. * Create a specialized Redis connection for the subscription.
  51. * Note that auto reconnection is enabled here.
  52. *
  53. * `enableReadyCheck` is also enabled because although subscription is allowed
  54. * while redis is loading data from the disk, we can check if the password
  55. * provided for the subscriber is correct, and if not, the current subscriber
  56. * will be disconnected and a new subscriber will be selected.
  57. */
  58. this.subscriber = new redis_1.default({
  59. port: options.port,
  60. host: options.host,
  61. password: options.password,
  62. enableReadyCheck: true,
  63. connectionName: SUBSCRIBER_CONNECTION_NAME,
  64. lazyConnect: true,
  65. tls: options.tls
  66. });
  67. // Ignore the errors since they're handled in the connection pool.
  68. this.subscriber.on("error", utils_1.noop);
  69. // Re-subscribe previous channels
  70. var previousChannels = { subscribe: [], psubscribe: [] };
  71. if (lastActiveSubscriber) {
  72. const condition = lastActiveSubscriber.condition || lastActiveSubscriber.prevCondition;
  73. if (condition && condition.subscriber) {
  74. previousChannels.subscribe = condition.subscriber.channels("subscribe");
  75. previousChannels.psubscribe = condition.subscriber.channels("psubscribe");
  76. }
  77. }
  78. if (previousChannels.subscribe.length ||
  79. previousChannels.psubscribe.length) {
  80. var pending = 0;
  81. for (const type of ["subscribe", "psubscribe"]) {
  82. var channels = previousChannels[type];
  83. if (channels.length) {
  84. pending += 1;
  85. debug("%s %d channels", type, channels.length);
  86. this.subscriber[type](channels)
  87. .then(() => {
  88. if (!--pending) {
  89. this.lastActiveSubscriber = this.subscriber;
  90. }
  91. })
  92. .catch(utils_1.noop);
  93. }
  94. }
  95. }
  96. else {
  97. this.lastActiveSubscriber = this.subscriber;
  98. }
  99. for (const event of ["message", "messageBuffer"]) {
  100. this.subscriber.on(event, (arg1, arg2) => {
  101. this.emitter.emit(event, arg1, arg2);
  102. });
  103. }
  104. for (const event of ["pmessage", "pmessageBuffer"]) {
  105. this.subscriber.on(event, (arg1, arg2, arg3) => {
  106. this.emitter.emit(event, arg1, arg2, arg3);
  107. });
  108. }
  109. }
  110. start() {
  111. this.started = true;
  112. this.selectSubscriber();
  113. debug("started");
  114. }
  115. stop() {
  116. this.started = false;
  117. if (this.subscriber) {
  118. this.subscriber.disconnect();
  119. this.subscriber = null;
  120. }
  121. debug("stopped");
  122. }
  123. }
  124. exports.default = ClusterSubscriber;