Server-side
var MIDServerLoadBalancer = Class.create();
MIDServerLoadBalancer.prototype = {
initialize: function() {
this.CLUSTER_NAME = 'production_cluster';
this.MAX_QUEUE_DEPTH = 50; // Maximum queued items per MID Server
this.HEALTH_CHECK_MINUTES = 5;
},
// Create or update MID Server pool with load balancing configuration
setupMIDServerPool: function(poolName, capabilities, midServerList) {
var pool = new GlideRecord('ecc_agent_cluster');
if (!pool.get('name', poolName)) {
pool.initialize();
pool.name = poolName;
}
pool.description = 'Load balanced pool for ' + capabilities.join(', ');
pool.load_balance = true;
pool.selection_method = 'load_balance'; // Options: round_robin, load_balance, affinity
pool.max_queue_depth = this.MAX_QUEUE_DEPTH;
pool.active = true;
var poolSysId = pool.update();
// Add MID Servers to pool
this._addServersToPool(poolSysId, midServerList);
// Set capabilities for the pool
this._setPoolCapabilities(poolSysId, capabilities);
gs.info('MID Server pool {0} configured with {1} servers', poolName, midServerList.length);
return poolSysId;
},
// Route work item to optimal MID Server based on capabilities and load
routeWorkItem: function(workType, requiredCapabilities, payload) {
var selectedMID = this._selectOptimalMIDServer(requiredCapabilities);
if (!selectedMID) {
gs.error('No available MID Server found for capabilities: {0}', requiredCapabilities.join(', '));
return null;
}
// Create ECC queue entry with MID Server affinity
var eccQueue = new GlideRecord('ecc_queue');
eccQueue.initialize();
eccQueue.agent = 'mid.server.' + selectedMID.name;
eccQueue.topic = workType;
eccQueue.name = workType + '_' + gs.generateGUID();
eccQueue.source = 'load_balancer';
eccQueue.payload = new XMLDocument(payload).toString();
eccQueue.state = 'ready';
eccQueue.agent_correlator = selectedMID.sys_id;
var queueSysId = eccQueue.insert();
// Update MID Server load tracking
this._updateServerLoad(selectedMID.sys_id, 1);
gs.info('Work item {0} routed to MID Server {1}', eccQueue.name, selectedMID.name);
return queueSysId;
},
// Monitor cluster health and rebalance if needed
monitorClusterHealth: function() {
var cluster = new GlideRecord('ecc_agent_cluster');
cluster.addQuery('name', this.CLUSTER_NAME);
cluster.query();
while (cluster.next()) {
var healthMetrics = this._calculateClusterMetrics(cluster.sys_id);
if (healthMetrics.unhealthyServers > 0) {
gs.warn('Cluster {0} has {1} unhealthy servers', cluster.name, healthMetrics.unhealthyServers);
this._redistributeLoad(cluster.sys_id);
}
// Log cluster status
gs.info('Cluster {0}: {1} active servers, avg load: {2}%',
cluster.name, healthMetrics.activeServers, healthMetrics.averageLoad);
}
},
_selectOptimalMIDServer: function(requiredCapabilities) {
var midServers = new GlideRecord('ecc_agent');
midServers.addQuery('status', 'Up');
midServers.addQuery('validated', 'true');
// Filter by capabilities if specified
if (requiredCapabilities && requiredCapabilities.length > 0) {
var capabilityQuery = midServers.addQuery('capabilities', 'CONTAINS', requiredCapabilities[0]);
for (var i = 1; i < requiredCapabilities.length; i++) {
capabilityQuery.addOrCondition('capabilities', 'CONTAINS', requiredCapabilities[i]);
}
}
midServers.orderBy('queue_count'); // Order by current load (ascending)
midServers.setLimit(1);
midServers.query();
if (midServers.next()) {
// Verify server isn't overloaded
if (parseInt(midServers.queue_count) < this.MAX_QUEUE_DEPTH) {
return {
sys_id: midServers.sys_id.toString(),
name: midServers.name.toString(),
currentLoad: parseInt(midServers.queue_count)
};
}
}
return null;
},
_addServersToPool: function(poolSysId, midServerList) {
// Remove existing pool members
var existingMembers = new GlideRecord('ecc_agent_cluster_member');
existingMembers.addQuery('cluster', poolSysId);
existingMembers.deleteMultiple();
// Add new members
for (var i = 0; i < midServerList.length; i++) {
var member = new GlideRecord('ecc_agent_cluster_member');
member.initialize();
member.cluster = poolSysId;
member.agent = midServerList[i];
member.active = true;
member.weight = 100; // Equal weight for load balancing
member.insert();
}
},
_setPoolCapabilities: function(poolSysId, capabilities) {
var pool = new GlideRecord('ecc_agent_cluster');
if (pool.get(poolSysId)) {
pool.capabilities = capabilities.join(',');
pool.update();
}
},
_updateServerLoad: function(midServerSysId, increment) {
var midServer = new GlideRecord('ecc_agent');
if (midServer.get(midServerSysId)) {
var currentLoad = parseInt(midServer.queue_count) || 0;
midServer.queue_count = currentLoad + increment;
midServer.update();
}
},
_calculateClusterMetrics: function(clusterSysId) {
var metrics = { activeServers: 0, unhealthyServers: 0, averageLoad: 0, totalLoad: 0 };
var members = new GlideRecord('ecc_agent_cluster_member');
members.addQuery('cluster', clusterSysId);
members.addQuery('active', true);
members.query();
while (members.next()) {
var agent = new GlideRecord('ecc_agent');
if (agent.get(members.agent)) {
if (agent.status == 'Up' && agent.validated == 'true') {
metrics.activeServers++;
metrics.totalLoad += parseInt(agent.queue_count) || 0;
} else {
metrics.unhealthyServers++;
}
}
}
if (metrics.activeServers > 0) {
metrics.averageLoad = Math.round((metrics.totalLoad / metrics.activeServers) * 100 / this.MAX_QUEUE_DEPTH);
}
return metrics;
},
_redistributeLoad: function(clusterSysId) {
// Implementation would move pending work items from failed servers to healthy ones
gs.info('Redistributing load for cluster: {0}', clusterSysId);
},
type: 'MIDServerLoadBalancer'
};
This implementation demonstrates creating MID Server pools with load balancing configuration, intelligent work routing based on server capabilities and current load, and cluster health monitoring. The key aspects include using the ecc_agent_cluster and ecc_agent_cluster_member tables to manage server pools, implementing capability-based routing by filtering MID Servers on required capabilities, and tracking server load through queue_count to ensure optimal distribution.