@celerispay/hazelcast-client 3.12.5-8 → 3.12.7-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES_UNCOMMITTED.md +53 -0
- package/FAULT_TOLERANCE_IMPROVEMENTS.md +208 -0
- package/HAZELCAST_CLIENT_EVOLUTION.md +402 -0
- package/lib/HeartbeatService.js +11 -2
- package/lib/PartitionService.d.ts +0 -3
- package/lib/PartitionService.js +3 -32
- package/lib/invocation/ClientConnection.js +41 -11
- package/lib/invocation/ClientConnectionManager.d.ts +54 -0
- package/lib/invocation/ClientConnectionManager.js +210 -4
- package/lib/invocation/ClusterService.d.ts +47 -0
- package/lib/invocation/ClusterService.js +164 -4
- package/lib/invocation/ConnectionAuthenticator.d.ts +11 -0
- package/lib/invocation/ConnectionAuthenticator.js +85 -12
- package/lib/invocation/CredentialPreservationService.d.ts +141 -0
- package/lib/invocation/CredentialPreservationService.js +377 -0
- package/lib/invocation/HazelcastFailoverManager.d.ts +102 -0
- package/lib/invocation/HazelcastFailoverManager.js +285 -0
- package/package.json +7 -6
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2008-2021, Hazelcast, Inc. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
/**
|
|
19
|
+
* Node state in the cluster
|
|
20
|
+
*/
|
|
21
|
+
var NodeState;
|
|
22
|
+
(function (NodeState) {
|
|
23
|
+
NodeState["UNKNOWN"] = "unknown";
|
|
24
|
+
NodeState["CONNECTING"] = "connecting";
|
|
25
|
+
NodeState["CONNECTED"] = "connected";
|
|
26
|
+
NodeState["OWNER"] = "owner";
|
|
27
|
+
NodeState["CHILD"] = "child";
|
|
28
|
+
NodeState["DISCONNECTED"] = "disconnected";
|
|
29
|
+
NodeState["FAILED"] = "failed";
|
|
30
|
+
})(NodeState = exports.NodeState || (exports.NodeState = {}));
|
|
31
|
+
/**
|
|
32
|
+
* Implements the Java client's failover behavior
|
|
33
|
+
* This ensures seamless node transitions and proper ownership handling
|
|
34
|
+
*/
|
|
35
|
+
var HazelcastFailoverManager = /** @class */ (function () {
|
|
36
|
+
function HazelcastFailoverManager(client, logger) {
|
|
37
|
+
this.nodes = new Map();
|
|
38
|
+
this.failoverInProgress = false;
|
|
39
|
+
this.failoverTimeout = 30000; // 30 seconds
|
|
40
|
+
this.currentOwner = null;
|
|
41
|
+
this.client = client;
|
|
42
|
+
this.logger = logger;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Registers a node in the cluster
|
|
46
|
+
* @param address The node address
|
|
47
|
+
* @param connection The connection to the node
|
|
48
|
+
* @param isOwner Whether this node is the owner
|
|
49
|
+
*/
|
|
50
|
+
HazelcastFailoverManager.prototype.registerNode = function (address, connection, isOwner) {
|
|
51
|
+
if (isOwner === void 0) { isOwner = false; }
|
|
52
|
+
var addressStr = address.toString();
|
|
53
|
+
var nodeInfo = {
|
|
54
|
+
address: address,
|
|
55
|
+
state: isOwner ? NodeState.OWNER : NodeState.CHILD,
|
|
56
|
+
connection: connection,
|
|
57
|
+
lastSeen: Date.now(),
|
|
58
|
+
failureCount: 0,
|
|
59
|
+
isOwner: isOwner
|
|
60
|
+
};
|
|
61
|
+
this.nodes.set(addressStr, nodeInfo);
|
|
62
|
+
if (isOwner) {
|
|
63
|
+
this.currentOwner = address;
|
|
64
|
+
this.logger.info('HazelcastFailoverManager', "Registered owner node: " + addressStr);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.logger.info('HazelcastFailoverManager', "Registered child node: " + addressStr);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Handles node disconnection gracefully
|
|
72
|
+
* @param address The disconnected node address
|
|
73
|
+
*/
|
|
74
|
+
HazelcastFailoverManager.prototype.handleNodeDisconnection = function (address) {
|
|
75
|
+
var addressStr = address.toString();
|
|
76
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
77
|
+
if (!nodeInfo) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.logger.warn('HazelcastFailoverManager', "Node " + addressStr + " disconnected, state: " + nodeInfo.state);
|
|
81
|
+
if (nodeInfo.isOwner) {
|
|
82
|
+
// Owner node disconnected - initiate failover
|
|
83
|
+
this.handleOwnerDisconnection(address);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Child node disconnected - just mark as disconnected
|
|
87
|
+
this.handleChildDisconnection(address);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Handles owner node disconnection
|
|
92
|
+
* @param address The disconnected owner node address
|
|
93
|
+
*/
|
|
94
|
+
HazelcastFailoverManager.prototype.handleOwnerDisconnection = function (address) {
|
|
95
|
+
var addressStr = address.toString();
|
|
96
|
+
this.logger.warn('HazelcastFailoverManager', "Owner node " + addressStr + " disconnected, initiating failover");
|
|
97
|
+
// Mark current owner as failed
|
|
98
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
99
|
+
if (nodeInfo) {
|
|
100
|
+
nodeInfo.state = NodeState.FAILED;
|
|
101
|
+
nodeInfo.connection = null;
|
|
102
|
+
}
|
|
103
|
+
this.currentOwner = null;
|
|
104
|
+
// Find the best candidate for new owner
|
|
105
|
+
var newOwner = this.selectNewOwner();
|
|
106
|
+
if (newOwner) {
|
|
107
|
+
this.promoteToOwner(newOwner);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.logger.error('HazelcastFailoverManager', 'No suitable node found for ownership, cluster may be down');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Handles child node disconnection
|
|
115
|
+
* @param address The disconnected child node address
|
|
116
|
+
*/
|
|
117
|
+
HazelcastFailoverManager.prototype.handleChildDisconnection = function (address) {
|
|
118
|
+
var addressStr = address.toString();
|
|
119
|
+
this.logger.info('HazelcastFailoverManager', "Child node " + addressStr + " disconnected");
|
|
120
|
+
// Mark as disconnected but keep in nodes map
|
|
121
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
122
|
+
if (nodeInfo) {
|
|
123
|
+
nodeInfo.state = NodeState.DISCONNECTED;
|
|
124
|
+
nodeInfo.connection = null;
|
|
125
|
+
}
|
|
126
|
+
// No failover needed for child nodes
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Selects the best candidate for new owner
|
|
130
|
+
* @returns The address of the best candidate, or null if none available
|
|
131
|
+
*/
|
|
132
|
+
HazelcastFailoverManager.prototype.selectNewOwner = function () {
|
|
133
|
+
var candidates = [];
|
|
134
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
135
|
+
if (nodeInfo.state === NodeState.CONNECTED &&
|
|
136
|
+
nodeInfo.connection &&
|
|
137
|
+
nodeInfo.connection.isAlive() &&
|
|
138
|
+
!nodeInfo.isOwner) {
|
|
139
|
+
candidates.push(nodeInfo);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
if (candidates.length === 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// Select the node with the lowest failure count and most recent connection
|
|
146
|
+
candidates.sort(function (a, b) {
|
|
147
|
+
if (a.failureCount !== b.failureCount) {
|
|
148
|
+
return a.failureCount - b.failureCount;
|
|
149
|
+
}
|
|
150
|
+
return b.lastSeen - a.lastSeen;
|
|
151
|
+
});
|
|
152
|
+
return candidates[0].address;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Promotes a node to owner
|
|
156
|
+
* @param address The address of the node to promote
|
|
157
|
+
*/
|
|
158
|
+
HazelcastFailoverManager.prototype.promoteToOwner = function (address) {
|
|
159
|
+
var addressStr = address.toString();
|
|
160
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
161
|
+
if (!nodeInfo) {
|
|
162
|
+
this.logger.error('HazelcastFailoverManager', "Cannot promote " + addressStr + " to owner - node not found");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.logger.info('HazelcastFailoverManager', "Promoting " + addressStr + " to owner");
|
|
166
|
+
// Update node state
|
|
167
|
+
nodeInfo.state = NodeState.OWNER;
|
|
168
|
+
nodeInfo.isOwner = true;
|
|
169
|
+
// Update current owner
|
|
170
|
+
this.currentOwner = address;
|
|
171
|
+
// Notify client of ownership change
|
|
172
|
+
this.client.getClusterService().handleOwnershipChange(address, nodeInfo.connection);
|
|
173
|
+
this.logger.info('HazelcastFailoverManager', "Ownership transferred to " + addressStr);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Handles node reconnection
|
|
177
|
+
* @param address The reconnected node address
|
|
178
|
+
* @param connection The new connection
|
|
179
|
+
*/
|
|
180
|
+
HazelcastFailoverManager.prototype.handleNodeReconnection = function (address, connection) {
|
|
181
|
+
var addressStr = address.toString();
|
|
182
|
+
var nodeInfo = this.nodes.get(addressStr);
|
|
183
|
+
if (!nodeInfo) {
|
|
184
|
+
// New node joining the cluster
|
|
185
|
+
this.registerNode(address, connection, false);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.logger.info('HazelcastFailoverManager', "Node " + addressStr + " reconnected, previous state: " + nodeInfo.state);
|
|
189
|
+
// Update connection and state
|
|
190
|
+
nodeInfo.connection = connection;
|
|
191
|
+
nodeInfo.lastSeen = Date.now();
|
|
192
|
+
if (nodeInfo.state === NodeState.FAILED) {
|
|
193
|
+
// Failed node recovered - treat as child
|
|
194
|
+
nodeInfo.state = NodeState.CHILD;
|
|
195
|
+
nodeInfo.isOwner = false;
|
|
196
|
+
this.logger.info('HazelcastFailoverManager', "Failed node " + addressStr + " recovered and rejoined as child");
|
|
197
|
+
}
|
|
198
|
+
else if (nodeInfo.state === NodeState.DISCONNECTED) {
|
|
199
|
+
// Disconnected node reconnected
|
|
200
|
+
nodeInfo.state = nodeInfo.isOwner ? NodeState.OWNER : NodeState.CHILD;
|
|
201
|
+
this.logger.info('HazelcastFailoverManager', "Disconnected node " + addressStr + " reconnected");
|
|
202
|
+
}
|
|
203
|
+
// Stop any ongoing failover/reconnection logic
|
|
204
|
+
this.stopFailoverLogic();
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Stops all failover and reconnection logic
|
|
208
|
+
*/
|
|
209
|
+
HazelcastFailoverManager.prototype.stopFailoverLogic = function () {
|
|
210
|
+
this.logger.info('HazelcastFailoverManager', 'Stopping all failover and reconnection logic - cluster stable');
|
|
211
|
+
// Reset failure counts for all nodes
|
|
212
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
213
|
+
nodeInfo.failureCount = 0;
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Gets the current owner address
|
|
218
|
+
* @returns The current owner address or null if no owner
|
|
219
|
+
*/
|
|
220
|
+
HazelcastFailoverManager.prototype.getCurrentOwner = function () {
|
|
221
|
+
return this.currentOwner;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Gets all connected nodes
|
|
225
|
+
* @returns Array of connected node addresses
|
|
226
|
+
*/
|
|
227
|
+
HazelcastFailoverManager.prototype.getConnectedNodes = function () {
|
|
228
|
+
var connected = [];
|
|
229
|
+
this.nodes.forEach(function (nodeInfo) {
|
|
230
|
+
if (nodeInfo.state === NodeState.CONNECTED ||
|
|
231
|
+
nodeInfo.state === NodeState.OWNER) {
|
|
232
|
+
connected.push(nodeInfo.address);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return connected;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Gets the cluster state summary
|
|
239
|
+
* @returns Summary of cluster state
|
|
240
|
+
*/
|
|
241
|
+
HazelcastFailoverManager.prototype.getClusterStateSummary = function () {
|
|
242
|
+
var summary = {
|
|
243
|
+
totalNodes: this.nodes.size,
|
|
244
|
+
currentOwner: this.currentOwner ? this.currentOwner.toString() : 'none',
|
|
245
|
+
connectedNodes: this.getConnectedNodes().map(function (addr) { return addr.toString(); }),
|
|
246
|
+
nodeStates: {}
|
|
247
|
+
};
|
|
248
|
+
this.nodes.forEach(function (nodeInfo, addressStr) {
|
|
249
|
+
summary.nodeStates[addressStr] = {
|
|
250
|
+
state: nodeInfo.state,
|
|
251
|
+
isOwner: nodeInfo.isOwner,
|
|
252
|
+
failureCount: nodeInfo.failureCount,
|
|
253
|
+
lastSeen: new Date(nodeInfo.lastSeen).toISOString()
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
return summary;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Cleans up failed nodes that haven't recovered
|
|
260
|
+
* @param maxFailureAge Maximum age for failed nodes (default: 5 minutes)
|
|
261
|
+
*/
|
|
262
|
+
HazelcastFailoverManager.prototype.cleanupFailedNodes = function (maxFailureAge) {
|
|
263
|
+
var _this = this;
|
|
264
|
+
if (maxFailureAge === void 0) { maxFailureAge = 300000; }
|
|
265
|
+
var now = Date.now();
|
|
266
|
+
var toRemove = [];
|
|
267
|
+
this.nodes.forEach(function (nodeInfo, addressStr) {
|
|
268
|
+
if (nodeInfo.state === NodeState.FAILED) {
|
|
269
|
+
var timeSinceFailure = now - nodeInfo.lastSeen;
|
|
270
|
+
if (timeSinceFailure > maxFailureAge) {
|
|
271
|
+
toRemove.push(addressStr);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (toRemove.length > 0) {
|
|
276
|
+
this.logger.info('HazelcastFailoverManager', "Cleaning up " + toRemove.length + " failed nodes");
|
|
277
|
+
toRemove.forEach(function (addressStr) {
|
|
278
|
+
_this.nodes.delete(addressStr);
|
|
279
|
+
_this.logger.debug('HazelcastFailoverManager', "Removed failed node: " + addressStr);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
return HazelcastFailoverManager;
|
|
284
|
+
}());
|
|
285
|
+
exports.HazelcastFailoverManager = HazelcastFailoverManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celerispay/hazelcast-client",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.7-2",
|
|
4
4
|
"description": "Hazelcast - open source In-Memory Data Grid - client for NodeJS with critical connection failover fixes",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,18 +38,19 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/celerispay/hazelcast-nodejs-client#readme",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"bluebird": "3.
|
|
42
|
-
"long": "
|
|
43
|
-
"
|
|
41
|
+
"bluebird": "^3.7.2",
|
|
42
|
+
"long": "^4.0.0",
|
|
43
|
+
"safe-buffer": "^5.2.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@types/bluebird": "^3.5.21",
|
|
47
|
+
"@types/long": "^3.0.32",
|
|
46
48
|
"@types/node": "8.0.0",
|
|
47
49
|
"mocha": "3.2.0",
|
|
48
50
|
"sinon": "4.0.0",
|
|
49
51
|
"tslint": "5.7.0",
|
|
50
52
|
"typedoc": "^0.16.11",
|
|
51
|
-
"typescript": "2.8.4"
|
|
52
|
-
"winston": "2.3.1"
|
|
53
|
+
"typescript": "2.8.4"
|
|
53
54
|
},
|
|
54
55
|
"typings": "./lib/index"
|
|
55
56
|
}
|