web-dev-qa-db-fra.com

comment compter le nombre de demandes dans la dernière seconde, minute et heure

Il existe un serveur Web hypothétique qui ne prend en charge qu'une API très simple: le nombre de demandes reçues au cours de la dernière heure, minute et seconde . Ce serveur est très populaire dans le monde et a reçu des milliers de demandes par seconde.

Vise à trouver comment retourner précisément ces 3 comptes à chaque demande?

Les demandes arrivant tout le temps, la fenêtre d'une heure, une minute et une seconde est donc différente par demande .. Comment gérer une fenêtre différente par demande afin que les comptages soient corrects par demande?

21
user2250246

Si une précision de 100% est requise:

Ayez une liste chaînée de toutes les demandes et 3 chefs d'accusation - pour la dernière heure, la dernière minute et la dernière seconde.

Vous aurez 2 pointeurs dans la liste chaînée - il y a une minute et une seconde.

Il y a une heure sera à la fin de la liste. Lorsque l'heure de la dernière demande est plus d'une heure avant l'heure actuelle, supprimez-la de la liste et décrémentez le nombre d'heures.

Les pointeurs minute et seconde indiqueront la première requête survenue il y a une minute et une seconde, respectivement. Lorsque le temps de la demande est supérieur à une minute/seconde avant l'heure actuelle, déplacez le pointeur vers le haut et décrémentez le compte minute/seconde.

Quand une nouvelle demande arrive, ajoutez-la aux 3 comptes et ajoutez-la au début de la liste.

Les demandes de comptage impliqueraient simplement de renvoyer les comptes.

Toutes les opérations ci-dessus sont amorties en temps constant.

Si une précision inférieure à 100% est acceptable:

La complexité de l'espace pour ce qui précède pourrait être un peu élevée, en fonction du nombre de demandes reçues par seconde; vous pouvez réduire ceci en sacrifiant légèrement la précision comme suit:

Ayez une liste chaînée comme ci-dessus, mais seulement pour la dernière seconde. Aussi avoir les 3 chefs d'accusation.

Ensuite, avoir un tableau circulaire de 60 éléments indiquant les comptes de chacune des 60 dernières secondes. Chaque fois qu'une seconde passe, soustrayez le dernier élément (le plus ancien) du tableau du compte des minutes et ajoutez le dernier décompte au tableau.

Avoir un tableau circulaire similaire pour les 60 dernières minutes.

Perte de précision: Le nombre de minutes peut être désactivé par toutes les demandes en une seconde et le nombre d'heures peut être désactivé par toutes les demandes en une minute.

Évidemment, cela n’aura pas vraiment de sens si vous n’avez qu’une demande par seconde ou moins. Dans ce cas, vous pouvez conserver la dernière minute dans la liste chaînée et ne disposer que d'un tableau circulaire pour les 60 dernières minutes.

Il existe également d'autres variantes: le rapport précision/espace utilisé peut être ajusté en fonction des besoins.

Une minuterie pour enlever les anciens éléments:

Si vous supprimez les anciens éléments uniquement lorsque de nouveaux éléments entrent, le temps d’amortissement sera amorti (certaines opérations peuvent durer plus longtemps, mais la moyenne est maintenue à temps constant).

Si vous voulez que le temps soit vrai et constant, vous pouvez également avoir une minuterie en marche qui supprime les anciens éléments, et chaque invocation de celle-ci (et bien sûr des insertions et des vérifications des comptes) ne prend que du temps constant, puisque vous supprimez éléments insérés dans le temps constant depuis le dernier tick de la minuterie.

26
Dukeling

Pour ce faire, pendant une fenêtre temporelle de T secondes, utilisez une structure de données de file d'attente dans laquelle vous pouvez mettre en file d'attente les horodatages de demandes individuelles au fur et à mesure de leur arrivée. Lorsque vous souhaitez lire le nombre de demandes arrivées au cours de la fenêtre la plus récente de T secondes, commencez par supprimer de "l'ancienne" fin de la file d'attente les horodatages antérieurs à T secondes, puis lisez la taille de la file d'attente. Vous devez également supprimer des éléments chaque fois que vous ajoutez une nouvelle demande à la file d'attente afin de conserver sa taille limitée (en supposant un débit limité pour les demandes entrantes).

Cette solution fonctionne avec une précision arbitraire, par ex. milliseconde précision. Si vous vous contentez de renvoyer des réponses approximatives, vous pouvez par exemple pour la fenêtre temporelle de T = 3600 (une heure), consolidez les demandes entrant dans la même seconde en un seul élément de file d'attente, ce qui limite la taille de la file d'attente à 3600. Je pense que cela serait plus que correct, mais perd théoriquement en précision. Pour T = 1, vous pouvez effectuer une consolidation au niveau de la milliseconde si vous le souhaitez.

En pseudocode:

queue Q

proc requestReceived()
  Q.insertAtFront(now())
  collectGarbage()

proc collectGarbage()
  limit = now() - T
  while (! Q.empty() && Q.lastElement() < limit)
    Q.popLast()

proc count()
  collectGarbage()
  return Q.size()
12
Antti Huima

Pourquoi ne pas simplement utiliser un tableau circulaire? Nous avons 3600 éléments dans ce tableau. 

index = 0;
Array[index % 3600] = count_in_one_second. 
++index;

si vous voulez la dernière seconde, retournez le dernier élément de ce tableau. si vous voulez la dernière minute, retournez la somme des 60 derniers éléments . si vous voulez la dernière heure, retournez la somme de tout le tableau (3600 éléments).

N'est-ce pas une solution simple et efficace?

Merci

Deryk

5
derek

Une solution est comme ceci:

1) Utilisez un tableau circulaire de longueur 3600 (60 * 60 secondes par heure) pour conserver les données de chaque seconde de la dernière heure. 

Pour enregistrer les données pendant une nouvelle seconde, déposez les données de la dernière seconde dans le tableau circulaire en déplaçant le pointeur principal de ce dernier.

2) Dans chaque élément du tableau circulaire, au lieu de conserver le nombre de demandes dans une seconde particulière, nous enregistrons la somme cumulée pour le nombre de demandes que nous voyons précédemment, et le nombre de demandes pour une période donnée peut être: calculé par requests_sum.get(current_second) - requests_sum.get(current_second - number_of_seconds_in_this_period)

Toutes les opérations telles que increament(), getCountForLastMinute(), getCountForLastHour() peuvent être effectuées en O(1) temps.

=============================================== ========================

Voici un exemple de la façon dont cela fonctionne.

Si nous avons un nombre de requêtes dans les 3 dernières secondes comme ceci: 1st second: 2 requests 2nd second: 4 requests 3rd second: 3 requests

Le tableau circulaire ressemblera à ceci: sum = [2, 6, 9] Où 6 = 4 + 2 et 9 = 2 + 4 + 3

Dans ce cas:

1) si vous voulez obtenir le nombre de requêtes de la dernière seconde (le nombre de requêtes de la 3ème seconde), calculez simplement sum[2] - sum[1] = 9 - 6 = 3

2) si vous voulez obtenir le nombre de requêtes des deux dernières secondes (le nombre de requêtes de la 3ème seconde et le nombre de requêtes de la 2ème seconde), calculez simplement sum[2] - sum[0] = 9 - 2 = 7

3
nybon

Vous pouvez créer un tableau de taille 60x60 pour chaque seconde de l'heure et l'utiliser comme tampon circulaire. Chaque entrée contient le nombre de demandes pour une seconde donnée. Lorsque vous passez à la seconde suivante, effacez-le et commencez à compter. Lorsque vous êtes à la fin du tableau, vous recommencez à 0. Vous devez donc effacer tous les comptes avant 1 heure. 

  1. Pour heure: retourne la somme de tous les éléments
  2. Pour minute: retourne la somme des 60 dernières entrées (de currentIndex)
  3. Pour la seconde: nombre de retours sur le currentIndex

Donc, tous les trois ont O(1) complexité d'espace et de temps. Le seul inconvénient est qu’il ignore les millisecondes, mais vous pouvez appliquer la même notion pour inclure également les millisecondes.

1
Azho KG

Je devais résoudre ce problème dans Go, et je ne pense pas avoir déjà vu cette approche, mais elle pourrait également être très spécifique à mon cas d'utilisation.

Comme il se connecte à une API tierce et doit limiter ses propres demandes, j'ai simplement gardé un compteur pour la dernière seconde et un compteur pour les 2 dernières minutes (les deux compteurs dont j'avais besoin).

var callsSinceLastSecond, callsSinceLast2Minutes uint64

Ensuite, je lançais mes demandes dans des routines distinctes lorsque les compteurs d'appels étaient inférieurs à ma limite autorisée.

for callsSinceLastSecond > 20 || callsSinceLast2Minutes > 100 {
    time.Sleep(10 * time.Millisecond)
}

Et à la fin de chaque routine, je décrémentais atomiquement le compteur. 

go func() {
    time.Sleep(1 * time.Second)
    atomic.AddUint64(&callsSinceLastSecond, ^uint64(0))
}()

go func() {
    time.Sleep(2 * time.Minute)
    atomic.AddUint64(&callsSinceLast2Minutes, ^uint64(0))
}()

Et cela semble fonctionner jusqu'à présent sans aucun problème avec des tests assez lourds jusqu'à présent.

1
Brian Leishman

La suite le code est en JS. Il vous retournera le compte en O (1). J'ai écrit ce programme pour une interview où le temps était prédéfini à 5 minutes. Mais vous pouvez modifier ce code pour les secondes, les minutes, etc. Faites-moi savoir comment ça se passe.

  1. Créer un objet qui aura la milliseconde comme clé et le compteur comme valeur
  2. Ajoutez une propriété appelée totalCount et prédéfinissez-la à 0
  3. Avec chaque journal des compteurs d’incrément de coups défini à l’étape 1 et le totalCount
  4. Ajoutez une méthode appelée clean_hits, appelez cette méthode toutes les millisecondes.
  5. Dans la méthode clean_hits, supprimez chaque entrée (en dehors de notre plage de temps) de l'objet créé et soustrayez ce nombre de totalCount avant de supprimer l'entrée. 

    this.hitStore = { "totalCount" : 0};

1
Nishchal joshi

Qu'en est-il d'une simple liste d'horodatages? Chaque fois que vous faites une demande, vous ajoutez l'horodatage actuel à la liste. Et chaque fois que vous voulez vérifier si vous êtes sous la limite de débit, vous supprimez d’abord les horodatages de plus d’une heure pour éviter le débordement de pile (hehe), puis vous comptez le nombre d’horodatages dans la dernière seconde, minute, peu importe.

Cela pourrait se faire facilement en Python:

import time

requestsTimestamps = []

def add_request():
    requestsTimestamps.append(time.time())

def requestsCount(delayInSeconds):
    requestsTimestamps = [t for t in requestsTimestamps if t >= time.time() - 3600]
    return len([t for t in requestsTimestamps if t >= time.time() - delayInSeconds])

Je suppose que cela peut être optimisé mais vous voyez l’idée. 

0
fbparis