Muy buenas, les saluda Miguel y para hoy les traigo otro nuevo post.
Índice
Comprender el patrón y la implementación de los interruptores automáticos en Opossum
Una solicitud podría acaparar sus recursos de 30 a 120 segundos
si no se le da una respuesta.
En una arquitectura de 2
niveles de un cliente y un servidor, esto podría no ser tan desastroso, pero si su servidor depende de muchos otros microservicios que a su vez dependen de otros microservicios, esta conexión persistente puede consumir recursos en cascada en toda su red.
Esta cascada catastrófica podría salvarse fácilmente mediante el patrón de disyuntor.
En términos generales, un disyuntor es un envoltorio de una función que busca errores y falla la solicitud de manera elegante después de que se alcanza un cierto umbral.
La función también mantiene un recuento de errores y * dispara *
el disyuntor bloqueando más solicitudes para llamar al servidor que no responde. Profundicemos un poco más en esto.
Rotura de circuito: comprensión de la biblioteca de zarigüeyas
// snippet from line 54-67 and 170-173 //https://github.com/nodeshift/opossum/blob/bd59b4860ce412608c520c757af1bf2b9398577b/lib/status.js#L146 // number of buckets in which the CB window should be divided this[BUCKETS] = options.rollingCountBuckets; // DEFAULT: 10 // window length of the CB this[TIMEOUT] = options.rollingCountTimeout; // DEFAULT: 10000 // Bucket where all the calculations will be stored this[WINDOW] = new Array(this[BUCKETS]); //QUEUE datastructure // rotating interval for the bucket const bucketInterval = Math.floor(this[TIMEOUT] / this[BUCKETS]); // rotate the window bucket this[BUCKET_INTERVAL] = setInterval(nextBucket(this[WINDOW]), bucketInterval); // rotating logic const nextBucket = window => _ => window.pop(); window.unshift(bucket());;
Aquí están los puntos clave del código:
- Haz una VENTANA con 10 cubos.
- Girar el cubo cada 1 segundo.
- Variables de inicialización de cola (variables predeterminadas de zarigüeya) a VENTANA y quitar de la cola el valor más antiguo.
- Almacene la referencia de intervalo en una variable para su uso posterior.
//very very very stripped down overview of call function line 442 https://github.com/nodeshift/opossum/blob/master/lib/circuit.js call (context, ...rest) { //emit('fire'); if (CACHE.get(this) !== undefined) { // emit('cacheHit') and return cache value } else if (this.options.cache) { // emit('cacheMiss'); } if (!this.closed && !this.pendingClose) { // CB is closed emit('reject', error); } // emit ('success') if done // emit ('timeout') if not done // emit ('failure') if not done }
Los siguientes puntos clave ocurren en el código.
- Emitir el evento de incendio tan pronto como ingresemos a la función.
- Si el almacenamiento en caché está habilitado y accedemos al caché, devolvemos el valor en caché.
- Si nos olvidamos de la caché, emitimos el evento
cacheMiss
. - Si el disyuntor no está cerrado, emita el evento rechazado.
- Si la función tiene éxito, aumente el contador de éxito para la ventana actual.
- Si se produce un tiempo de espera o una falla, emita el evento de falla y tiempo de espera.
//copied from line 93 // https://github.com/nodeshift/opossum/blob/master/lib/circuit.js // Function that return the default values for a bucket const bucket = _ => ({ failures: 0, fallbacks: 0, successes: 0, rejects: 0, fires: 0, timeouts: 0, cacheHits: 0, cacheMisses: 0, semaphoreRejections: 0, percentiles: {}, latencyTimes: []}); get stats () { const totals = this[WINDOW].reduce((acc, val) => { if (!val) { return acc; } Object.keys(acc).forEach(key => (acc[key] += val[key] || 0)); return acc; }, bucket()); return totals; }
Solicitar estadísticas de la zarigüeya hace que se agreguen las estadísticas del grupo que ha sido establecido por la opción rollingCountBuckets
. Simplemente agrega todo el valor en la ventana y devuelve un solo objeto con toda la suma.
//this code looks almost same lol from line 678 -703 https://github.com/nodeshift/opossum/blob/master/lib/circuit.js function fail (circuit, err, args, latency) { circuit.emit('failure'); const stats = circuit.stats; if ((stats.fires < circuit.volumeThreshold) && !circuit.halfOpen) return; const errorRate = stats.failures / stats.fires * 100; if (errorRate > circuit.options.errorThresholdPercentage || circuit.halfOpen) { circuit.open(); } }
Cuando el código falla le preguntamos a stats ()
y comprobamos si la tasa de error es menor que el umbral establecido por nosotros. Si se cruza el umbral, abrimos el disyuntor. Que luego emite el evento 'abierto'
.
//again a little stripped down code from line 178 -197 https://github.com/nodeshift/opossum/blob/master/lib/circuit.js function _startTimer (circuit) { return _ => { const timer = circuit[RESET_TIMEOUT] = setTimeout(() => { circuit[STATE] = HALF_OPEN; circuit[PENDING_CLOSE] = true; circuit.emit('halfOpen', circuit.options.resetTimeout); }, circuit.options.resetTimeout); }; } this.on('open', _startTimer(this)); this.on('success', _ => { if (this.halfOpen) { this.close(); } }); }
Media apertura si el estado en el que el disyuntor intenta reactivarse. Aquí escuchamos el evento abierto y ponemos en marcha un temporizador tan pronto como se inicia el evento.
Una vez finalizado el tiempo de espera, comprobamos si cambia el estado del interruptor automático a medio abierto. Si la llamada tiene éxito, cerramos el disyuntor y restablecemos todos los valores.
TLDR: disyuntor
Cerrado: Cuando el disyuntor funciona normalmente. Abierto: Cuando se dispara el disyuntor. Medio abierto: Cuando el disyuntor intenta recuperarse. La ejecución de los eventos es la siguiente: Gracias por leer este artículo.3
estados:
Usando
Opossum
en su proyecto de JavaScript
const CircuitBreaker = require('opossum');
function asyncFunctionThatCouldFail (x, y) {
return new Promise((resolve, reject) => {
// Do something, maybe on the network or a disk
});
}
const options = {
timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure
errorThresholdPercentage: 50, // When 50% of requests fail, trip the circuit
resetTimeout: 30000 // After 30 seconds, try again.
};
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);
breaker.fire(params)
.then(console.log)
.catch(console.error);
Añadir comentario