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
Variables iniciales
// 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
1.Haz una VENTANA con 10 cubos
2. Girar el cubo cada 1 segundo
3. Variables de inicialización de cola (variables predeterminadas de zarigüeya) a VENTANA y quitar de la cola el valor más antiguo
4. Almacene la referencia de intervalo en una variable para su uso posterior
Disparando en zarigüeya
//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.
1. emitir el evento de incendio tan pronto como ingresemos a la función
2. Si el almacenamiento en caché está habilitado y accedemos al caché, devolvemos el valor en caché
3. Si nos olvidamos de la caché, emitimos el evento cacheMiss
4. Si el disyuntor no está cerrado, emita el evento rechazado.
5. Si la función tiene éxito, aumente el contador de éxito para la ventana actual.
6. Si se produce un tiempo de espera o una falla, emita el evento de falla y tiempo de espera,
Recopilación de estadísticas de la ventana
//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 el rollingCountBuckets opción. Simplemente agrega todo el valor en la ventana y devuelve un solo objeto con toda la suma.
Defecto
//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’.
Media apertura
//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
Un disyuntor gira entre 3 estados:
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:
1: El disyuntor permanece cerrado mientras los eventos sean exitosos.
2. Aumente el número de errores en la ventana tan pronto como una llamada se agote o falle.
3. Si el umbral de errores en la ventana aumenta un cierto límite, disparamos el disyuntor.
4. Después de un cierto intervalo, el interruptor automático intenta recuperarse y entra en el estado semiabierto.
5. Si la llamada subsiguiente falla, el disyuntor vuelve al estado abierto.
6. Si la llamada subsiguiente tiene éxito, el disyuntor pasa al estado abierto.
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 tener en cuenta: al pie: cada npm install
es un costo agregado a la memoria del servidor y al uso de la CPU. Comprender profundamente la biblioteca que instala no solo puede ayudarlo a ahorrar en el costo, sino también a encontrar cuellos de botella y permitirle usar la biblioteca de manera efectiva.
Gracias por leer este artículo.
Añadir comentario