Structure détaillée des données HyperLogLog de Reids

Contexte

Réaliser les données UV d'une page statistique, les visites de l'utilisateur de chaque page (les demandes multiples d'un même utilisateur ne sont comptées qu'une seule fois). Alors, comment implémenter cette fonction?

Peut-être que certains camarades de classe diront que nous utilisons tous growIO, nous n'avons pas besoin de réaliser le coût trop élevé par nous-mêmes, utilisez simplement les autres. C'est aussi bien.

Aujourd'hui, regardons comment nous pouvons répondre à cette demande et lutter contre des services TPS plus élevés?

Conception

  1. Puisque les statistiques des utilisateurs de la page ne sont pas répétées, nous choisissons une structure de données qui est la collection SET pour le stockage. Stockez l'ID utilisateur, s'il s'agit d'un utilisateur qui n'est pas connecté, générez-en un aléatoirement (à l'aide d'un horodatage, etc.) et stockez-le dans l'ensemble. Pour être plus rapide, est-ce basé sur la mémoire? Mais il est impossible d'utiliser la mémoire de ses propres services, puis d'utiliser la mémoire de services tiers, puis d'utiliser redis et d'avoir sa propre structure de données de collecte d'ensemble.
  2. Une fois que vous avez sélectionné la direction et le middleware, vous devez considérer la question du volume. Les statistiques sont toutes des pages chaudes. S'il y a des dizaines de millions d'UV par jour, vous devez perdre beaucoup d'espace pour le stockage. , Est-ce que ça vaut le coup? Il n'y a pas non plus besoin de données trop précises juste pour transmettre le volume de requêtes, alors avons-nous une meilleure structure de données?
  3. Redis fournit la structure de données HyperLogLog pour résoudre
    ce problème statistique

HyPerLogLog

introduction

HyperLogLog fournit une solution de comptage de déduplication inexacte. Bien qu'elle soit inexacte mais pas très
inexacte, l'erreur standard est de 0,81%. Cette précision peut déjà répondre aux exigences statistiques UV ci-dessus.
La structure de données HyperLogLog est une structure de données de haut niveau de Redis. Elle est très utile. Aujourd'hui, nous allons examiner de plus près (celle avec neuf bas-fonds et un profond).

utilisation

  • HyperLogLog fournit trois commandes, pfadd et pfcount. Pfmerge est bien compris à partir de la signification littérale. L'une consiste à augmenter le nombre et l'autre à obtenir le nombre. L'utilisation de pfadd est la même que le sadd de la collection d'ensemble. Lorsqu'un ID utilisateur arrive, l'ID utilisateur est entré. Oui. L'utilisation de pfcount et de scar est la même. Obtenez la valeur de comptage directement. Et pfmerge signifie littéralement fusionner, c'est-à-dire lorsque les statistiques de deux clés sont combinées en une seule clé (le nombre de statistiques lorsque les deux pages sont combinées)
  • Mais certaines personnes demanderont pourquoi un autre pf? HL n'est-il pas préférable de jouer dans des routines régulières? Ce PF est l'orthographe de l'inventeur de cette structure de données.
  • Conformément aux exigences commerciales ci-dessus, utilisez la page comme clé pour placer l'iD utilisateur dans HyPerLogLog à des fins de statistiques.
127.0.0.1:6379> pfadd codehole user1
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 1
127.0.0.1:6379> pfadd codehole user2
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 2
127.0.0.1:6379> pfadd codehole user3
127.0.0.1:6379> pfcount codehole
(integer) 3
127.0.0.1:6379> pfadd codehole user4
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 4
127.0.0.1:6379> pfadd codehole user5
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 5
127.0.0.1:6379> pfadd codehole user6
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 6
127.0.0.1:6379> pfadd codehole user7 user8 user9 user10
(integer) 1
127.0.0.1:6379> pfcount codehole
(integer) 10

Testez-le et il se sent assez précis. On a l'impression que c'est souvent faux.
Faisons de nombreux tests via des scripts:

public class JedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis();
for (int i = 0; i < 100000; i++) {
jedis.pfadd("codehole", "user" + i);
}
long total = jedis.pfcount("codehole");
System.out.printf("%d %d\n", 100000, total);
jedis.close();
} }

Après l'exécution, vérifiez: 100000 99723 et
relancez-le: 100000 99723
ne change pas, indiquant qu'il est effectivement lourd.
La différence est de 277 et le pourcentage est de 0,277%

Principe de réalisation

Plusieurs concepts importants:

  1. HyperLogLog ne stocke pas réellement la valeur de chaque élément. Il utilise un algorithme de probabilité pour calculer le nombre d'éléments en stockant la position du premier 1 de la valeur de hachage de l'élément.

  2. Expérience de Bernoulli: signifie probablement réaliser N taquiner la fille, rien de plus que taquiner la fille et ne pas taquiner la fille (succès et échec sont tous les deux à 50%, pas d'autres facteurs). Cela compte comme un tour si vous obtenez une fille (par exemple, si vous avez 100 fois sans succès et 101 fois, alors ces 101 fois ne sont qu'un tour). Donc N rounds ont été effectués, et le plus long a été dit à mes copains, et laissez vos copains deviner combien de rounds ont été effectués au total? (Autrement dit, devinez ce qu'est N). Un grand gars a également rencontré ce problème et a obtenu une formule grâce à de nombreuses expériences, qui peuvent être calculées. Pour plus de détails, cliquez sur (https://zhuanlan.zhihu.com/p/58519480)
    Insérez la description de l'image ici

  3. La signification de la figure ci-dessous est que, étant donné une série d'entiers aléatoires, nous enregistrons la longueur maximale k des bits zéro consécutifs bas, et
    le nombre de nombres aléatoires peut être estimé par cette valeur k. (Ce nombre aléatoire est analogue à notre identifiant d'utilisateur, et la longueur maximale K est le nombre de répétitions de ce nombre aléatoire sur le seau calculé par hachage (compréhension personnelle)) Il
    peut être comparé à l'expérience de Bernoulli ci-dessus.
    Insérez la description de l'image ici

4. Dans l'ensemble, nous pouvons obtenir des résultats statistiques qui ne sont pas très différents grâce à des formules de calcul de probabilité sans stocker de valeurs.

Implémentation de code simple

  1. Une formule de calcul simple mise en œuvre (pour aider à comprendre):
import java.util.concurrent.ThreadLocalRandom;

public class PfTest {
    static class BitKeeper {
        private int maxbits;

        public void random(long value) {
            int bits = lowZeros(value);
            if (bits > this.maxbits) {
                this.maxbits = bits;
            }
        }

        private int lowZeros(long value) {
            int i = 1;
            for (; i < 32; i++) {
                if (value >> i << i != value) {
                    break;
                }
            }
            return i - 1;
        }
    }

    static class Experiment {
        private int n;
        private int k;
        private BitKeeper[] keepers;

        public Experiment(int n) {
            this(n, 1024);
        }

        public Experiment(int n, int k) {
            this.n = n;
            this.k = k;
            this.keepers = new BitKeeper[k];
            for (int i = 0; i < k; i++) {
                this.keepers[i] = new BitKeeper();
            }
        }

        public void work() {
            for (int i = 0; i < this.n; i++) {
                long m = ThreadLocalRandom.current().nextLong(1L << 32);
                BitKeeper keeper = keepers[(int) (((m & 0xfff0000) >> 16) % keepers.length)];
                keeper.random(m);
            }
        }

        public double estimate() {
            double sumbitsInverse = 0.0;
            for (BitKeeper keeper : keepers) {
                sumbitsInverse += 1.0 / (float) keeper.maxbits;
            }
            double avgBits = (float) keepers.length / sumbitsInverse;
            return Math.pow(2, avgBits) * this.k;
        }
    }

    public static void main(String[] args) {
        for (int i = 100000; i < 1000000; i += 100000) {
            Experiment exp = new Experiment(i);
            exp.work();
            double est = exp.estimate();
            System.out.printf("%d %.2f %.2f\n", i, est, Math.abs(est - i) / i);
        }
    }
}

Pour résumer

  1. HyperLogLog peut obtenir des statistiques UV haute performance, mais il y aura une erreur de pourcentage
  2. Trois commandes de HyperLogLog pfadd pfcount pfmerge
  3. Le principe général d'HyperLogLog obtient le résultat grâce à des statistiques de probabilité (expérience de Bernoulli) sans enregistrer de valeurs spécifiques
  4. Utilisez Java pour implémenter simplement des statistiques de probabilité et calculer la quantité de nombres aléatoires.

Plus d'articles en cuir

https://zhuanlan.zhihu.com/p/58519480
https://en.wikipedia.org/wiki/HyperLogLog

"L'aventure profonde de Redis" -Book

Je suppose que tu aimes

Origine blog.csdn.net/weixin_40413961/article/details/108090463
conseillé
Classement