Fix circuit breaking functionality

This commit is contained in:
Michael Lumish 2021-05-20 10:31:00 -07:00
parent ec7c819181
commit 43a3bad549
3 changed files with 63 additions and 18 deletions

View File

@ -196,6 +196,18 @@ const currentConfig: ClientConfiguration = {
let anyCallSucceeded = false; let anyCallSucceeded = false;
const accumulatedStats: LoadBalancerAccumulatedStatsResponse = { const accumulatedStats: LoadBalancerAccumulatedStatsResponse = {
num_rpcs_started_by_method: {
EMPTY_CALL: 0,
UNARY_CALL: 0
},
num_rpcs_succeeded_by_method: {
EMPTY_CALL: 0,
UNARY_CALL: 0
},
num_rpcs_failed_by_method: {
EMPTY_CALL: 0,
UNARY_CALL: 0
},
stats_per_method: { stats_per_method: {
EMPTY_CALL: { EMPTY_CALL: {
rpcs_started: 0, rpcs_started: 0,
@ -208,14 +220,28 @@ const accumulatedStats: LoadBalancerAccumulatedStatsResponse = {
} }
}; };
function addAccumulatedCallStarted(callName: string) {
accumulatedStats.stats_per_method![callName].rpcs_started! += 1;
accumulatedStats.num_rpcs_started_by_method![callName] += 1;
}
function addAccumulatedCallEnded(callName: string, result: grpc.status) {
accumulatedStats.stats_per_method![callName].result![result] = (accumulatedStats.stats_per_method![callName].result![result] ?? 0) + 1;
if (result === grpc.status.OK) {
accumulatedStats.num_rpcs_succeeded_by_method![callName] += 1;
} else {
accumulatedStats.num_rpcs_failed_by_method![callName] += 1;
}
}
const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = {
UnaryCall: {}, UnaryCall: {},
EmptyCall: {} EmptyCall: {}
} }
function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) {
const callTypeStats = accumulatedStats.stats_per_method![callTypeEnumMapReverse[type]]; const callEnumName = callTypeEnumMapReverse[type];
callTypeStats.rpcs_started! += 1; addAccumulatedCallStarted(callEnumName);
const notifier = callStatsTracker.startCall(); const notifier = callStatsTracker.startCall();
let gotMetadata: boolean = false; let gotMetadata: boolean = false;
let hostname: string | null = null; let hostname: string | null = null;
@ -235,7 +261,7 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail
} else { } else {
callTimeHistogram[type][statusCode][duration[0]] = 1; callTimeHistogram[type][statusCode][duration[0]] = 1;
} }
callTypeStats.result![statusCode] = (callTypeStats.result![statusCode] ?? 0) + 1; addAccumulatedCallEnded(callEnumName, statusCode);
if (error) { if (error) {
if (failOnFailedRpcs && anyCallSucceeded) { if (failOnFailedRpcs && anyCallSucceeded) {
console.error('A call failed after a call succeeded'); console.error('A call failed after a call succeeded');

View File

@ -80,11 +80,17 @@ export class CdsLoadBalancer implements LoadBalancer {
this.watcher = { this.watcher = {
onValidUpdate: (update) => { onValidUpdate: (update) => {
this.latestCdsUpdate = update; this.latestCdsUpdate = update;
let maxConcurrentRequests: number | undefined = undefined;
for (const threshold of update.circuit_breakers?.thresholds ?? []) {
if (threshold.priority === 'DEFAULT') {
maxConcurrentRequests = threshold.max_requests?.value;
}
}
/* the lrs_server.self field indicates that the same server should be /* the lrs_server.self field indicates that the same server should be
* used for load reporting as for other xDS operations. Setting * used for load reporting as for other xDS operations. Setting
* lrsLoadReportingServerName to the empty string sets that behavior. * lrsLoadReportingServerName to the empty string sets that behavior.
* Otherwise, if the field is omitted, load reporting is disabled. */ * Otherwise, if the field is omitted, load reporting is disabled. */
const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig(update.name, [], [], update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, update.lrs_server?.self ? '' : undefined); const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig(update.name, [], [], update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, update.lrs_server?.self ? '' : undefined, maxConcurrentRequests);
trace('Child update EDS config: ' + JSON.stringify(edsConfig)); trace('Child update EDS config: ' + JSON.stringify(edsConfig));
this.childBalancer.updateAddressList( this.childBalancer.updateAddressList(
[], [],

View File

@ -37,6 +37,7 @@ import { Watcher } from './xds-stream-state/xds-stream-state';
import Filter = experimental.Filter; import Filter = experimental.Filter;
import BaseFilter = experimental.BaseFilter; import BaseFilter = experimental.BaseFilter;
import FilterFactory = experimental.FilterFactory; import FilterFactory = experimental.FilterFactory;
import FilterStackFactory = experimental.FilterStackFactory;
import CallStream = experimental.CallStream; import CallStream = experimental.CallStream;
const TRACER_NAME = 'eds_balancer'; const TRACER_NAME = 'eds_balancer';
@ -204,27 +205,42 @@ export class EdsLoadBalancer implements LoadBalancer {
* Otherwise, delegate picking the subchannel to the child * Otherwise, delegate picking the subchannel to the child
* balancer. */ * balancer. */
if (dropCategory === null) { if (dropCategory === null) {
return originalPicker.pick(pickArgs); const originalPick = originalPicker.pick(pickArgs);
let extraFilterFactory: FilterFactory<Filter> = new CallTrackingFilterFactory(() => {
this.concurrentRequests -= 1;
});
if (originalPick.extraFilterFactory) {
extraFilterFactory = new FilterStackFactory([originalPick.extraFilterFactory, extraFilterFactory]);
}
return {
pickResultType: originalPick.pickResultType,
status: originalPick.status,
subchannel: originalPick.subchannel,
onCallStarted: () => {
originalPick.onCallStarted?.();
this.concurrentRequests += 1;
},
extraFilterFactory: extraFilterFactory
};
} else { } else {
let details: string;
if (dropCategory === true) { if (dropCategory === true) {
details = 'Call dropped by load balancing policy.';
this.clusterDropStats?.addUncategorizedCallDropped(); this.clusterDropStats?.addUncategorizedCallDropped();
} else { } else {
details = `Call dropped by load balancing policy. Category: ${dropCategory}`;
this.clusterDropStats?.addCallDropped(dropCategory); this.clusterDropStats?.addCallDropped(dropCategory);
} }
return { return {
pickResultType: PickResultType.DROP, pickResultType: PickResultType.DROP,
status: { status: {
code: Status.UNAVAILABLE, code: Status.UNAVAILABLE,
details: `Call dropped by load balancing policy. Category: ${dropCategory}`, details: details,
metadata: new Metadata(), metadata: new Metadata(),
}, },
subchannel: null, subchannel: null,
extraFilterFactory: new CallTrackingFilterFactory(() => { extraFilterFactory: null,
this.concurrentRequests -= 1; onCallStarted: null
}),
onCallStarted: () => {
this.concurrentRequests += 1;
},
}; };
} }
}, },
@ -265,15 +281,12 @@ export class EdsLoadBalancer implements LoadBalancer {
* output, as a sentinel value indicating a drop with no category. * output, as a sentinel value indicating a drop with no category.
*/ */
private checkForDrop(): string | true | null { private checkForDrop(): string | true | null {
if (this.lastestConfig && this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) {
return true;
}
if (!this.latestEdsUpdate?.policy) { if (!this.latestEdsUpdate?.policy) {
return null; return null;
} }
if (!this.lastestConfig) {
return null;
}
if (this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) {
return true;
}
/* The drop_overloads policy is a list of pairs of category names and /* The drop_overloads policy is a list of pairs of category names and
* probabilities. For each one, if the random number is within that * probabilities. For each one, if the random number is within that
* probability range, we drop the call citing that category. Otherwise, the * probability range, we drop the call citing that category. Otherwise, the