菜鸟笔记
提升您的技术认知

redis数据结构之hperloglog-ag真人游戏

一、hyperloglog

hyperloglog是用来做基数统计的。

其可以非常省内存的去统计各种计数,比如注册ip数、每日访问ip数、页面实时uv(pv肯定字符串就搞定了)、在线用户数等在对准确性不是很重要的应用场景。

 

hyperloglog的优点是:

在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的,

hyperloglog的缺点:

它是估计基数的算法,所以会有一定误差0.81%。

每个hyperloglog键只需要花费12kb内存,就可以计算接近264个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 

但是,因为 hyperloglog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 hyperloglog 不能像集合那样,返回输入的各个元素即无法知道统计的详细内容。

 

二、基数和估算值

1、基数

基数是集合中不同元素的数量。

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 

基数估计就是在误差可接受的范围内,快速计算基数。

 

2、估算值

算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合理的范围之内。

 

三、hperloglog基本命令

redis hyperloglog 的基本命令:

1 pfadd key element [element ...] 

添加指定元素到 hyperloglog 中。

2 pfcount key [key ...] 

返回给定 hyperloglog 的基数估算值。

3 pfmerge destkey sourcekey [sourcekey ...] 

将多个 hyperloglog 合并为一个 hyperloglog

 

pfadd

将任意数量的元素添加到指定的 hyperloglog 里面。在执行这个命令之后,hyperloglog内部的结构会被更新,并有所反馈,

如果执行完之后hyperloglog内部的基数估算发生了变化,那么就会返回1,否则(认为已经存在)就返回0。

这个命令还有一个比较神器的就是可以只有键,没有值,这样的意思就是只是创建空的键,不放值。

如果这个键存在,不做任何事情,返回0;不存在的话就创建,并返回1。

这个命令的时间复杂度为o(1),所以就放心用吧~

 

pfcount

当命令作用于单个键的时候,返回这个键的基数估算值。如果键不存在,则返回0。

当 pfcount 命令作用于多个键时, 返回所有给定 hyperloglog 的并集的近似基数, 这个近似基数是通过将所有给定 hyperloglog 合并至一个临时 hyperloglog 来计算得出的。

这个命令在作用于单个值的时候,时间复杂度为o(1),并且具有非常低的平均常数时间;在作用于n个值的时候,时间复杂度为o(n),这个命令的常数复杂度会比较低些。

命令返回的可见集合(observed set)基数并不是精确值, 而是一个带有 0.81% 标准错误(standard error)的近似值。

举个例子, 为了记录一天会执行多少次各不相同的搜索查询, 一个程序可以在每次执行搜索查询时调用一次 pfadd , 并通过调用 pfcount 命令来获取这个记录的近似结果。

 

pfmerge

合并(merge)多个hyperloglog为一个hyperloglog。 合并后的 hyperloglog 的基数接近于所有输入 hyperloglog 的可见集合(observed set)的并集。

合并得出的 hyperloglog 会被储存在 destkey 键里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的 hyperloglog 。

这个命令的第一个参数为目标键,剩下的参数为要合并的hyperloglog。命令执行时,如果目标键不存在,则创建后再执行合并。

这个命令的时间复杂度为o(n),其中n为要合并的hyperloglog的个数。不过这个命令的常数时间复杂度比较高。

 

redis> pfadd  ip:20170626  "192.168.0.10"  "192.168.0.20"  "192.168.0.30"

(integer) 1

redis> pfadd  ip:20170626 "192.168.0.20"  "192.168.0.40"  "192.168.0.50"  # 存在就只加新的

(integer) 1

redis> pfcount ip:20170626  # 元素估计数量没有变化

(integer) 5

redis> pfadd  ip:20170626 "192.168.0.20"  # 存在就不会增加

(integer) 0

edis> pfmerge ip:20170626   ip:20170627   ip:20170628

ok

redis> pfcount  ip:201706

(integer) 5

 

四、hperloglog 描述

由于hperloglog,这种数据结构在实际应用场景中并不多。因此,这里就不再详细讨论了。

我们看下hperloglog.c文件,对hperloglog的描述

/* the redis hyperloglog implementation is based on the following ideas:

 *

 * * the use of a 64 bit hash function as proposed in [1], in order to don't

 *   limited to cardinalities up to 10^9, at the cost of just 1 additional

 *   bit per register.

 * * the use of 16384 6-bit registers for a great level of accuracy, using

 *   a total of 12k per key.

 * * the use of the redis string data type. no new type is introduced.

 * * no attempt is made to compress the data structure as in [1]. also the

 *   algorithm used is the original hyperloglog algorithm as in [2], with

 *   the only difference that a 64 bit hash function is used, so no correction

 *   is performed for values near 2^32 as in [1].

 *

 * [1] heule, nunkesser, hall: hyperloglog in practice: algorithmic

 *     engineering of a state of the art cardinality estimation algorithm.

 *

 * [2] p. flajolet, éric fusy, o. gandouet, and f. meunier. hyperloglog: the

 *     analysis of a near-optimal cardinality estimation algorithm.

 *

 * redis uses two representations:

 *

 * 1) a "dense" representation where every entry is represented by

 *    a 6-bit integer.

 * 2) a "sparse" representation using run length compression suitable

 *    for representing hyperloglogs with many registers set to 0 in

 *    a memory efficient way.

 *

 *

 * hll header

 * ===

 *

 * both the dense and sparse representation have a 16 byte header as follows:

 *

 * ------ --- ----- ----------

 * | hyll | e | n/u | cardin.  |

 * ------ --- ----- ----------

 *

 * the first 4 bytes are a magic string set to the bytes "hyll".

 * "e" is one byte encoding, currently set to hll_dense or

 * hll_sparse. n/u are three not used bytes.

 *

 * the "cardin." field is a 64 bit integer stored in little endian format

 * with the latest cardinality computed that can be reused if the data

 * structure was not modified since the last computation (this is useful

 * because there are high probabilities that hlladd operations don't

 * modify the actual data structure and hence the approximated cardinality).

 *

 * when the most significant bit in the most significant byte of the cached

 * cardinality is set, it means that the data structure was modified and

 * we can't reuse the cached value that must be recomputed.

 *

 * dense representation

 * ===

 *

 * the dense representation used by redis is the following:

 *

 * -------- -------- -------- ------//      //--

 * |11000000|22221111|33333322|55444444 ....     |

 * -------- -------- -------- ------//      //--

 *

 * the 6 bits counters are encoded one after the other starting from the

 * lsb to the msb, and using the next bytes as needed.

 *

 * sparse representation

 * ===

 *

 * the sparse representation encodes registers using a run length

 * encoding composed of three opcodes, two using one byte, and one using

 * of two bytes. the opcodes are called zero, xzero and val.

 *

 * zero opcode is represented as 00xxxxxx. the 6-bit integer represented

 * by the six bits 'xxxxxx', plus 1, means that there are n registers set

 * to 0. this opcode can represent from 1 to 64 contiguous registers set

 * to the value of 0.

 *

 * xzero opcode is represented by two bytes 01xxxxxx yyyyyyyy. the 14-bit

 * integer represented by the bits 'xxxxxx' as most significant bits and

 * 'yyyyyyyy' as least significant bits, plus 1, means that there are n

 * registers set to 0. this opcode can represent from 0 to 16384 contiguous

 * registers set to the value of 0.

 *

 * val opcode is represented as 1vvvvvxx. it contains a 5-bit integer

 * representing the value of a register, and a 2-bit integer representing

 * the number of contiguous registers set to that value 'vvvvv'.

 * to obtain the value and run length, the integers vvvvv and xx must be

 * incremented by one. this opcode can represent values from 1 to 32,

 * repeated from 1 to 4 times.

 *

 * the sparse representation can't represent registers with a value greater

 * than 32, however it is very unlikely that we find such a register in an

 * hll with a cardinality where the sparse representation is still more

 * memory efficient than the dense representation. when this happens the

 * hll is converted to the dense representation.

 *

 * the sparse representation is purely positional. for example a sparse

 * representation of an empty hll is just: xzero:16384.

 *

 * an hll having only 3 non-zero registers at position 1000, 1020, 1021

 * respectively set to 2, 3, 3, is represented by the following three

 * opcodes:

 *

 * xzero:1000 (registers 0-999 are set to 0)

 * val:2,1    (1 register set to value 2, that is register 1000)

 * zero:19    (registers 1001-1019 set to 0)

 * val:3,2    (2 registers set to value 3, that is registers 1020,1021)

 * xzero:15362 (registers 1022-16383 set to 0)

 *

 * in the example the sparse representation used just 7 bytes instead

 * of 12k in order to represent the hll registers. in general for low

 * cardinality there is a big win in terms of space efficiency, traded

 * with cpu time since the sparse representation is slower to access:

 *

 * the following table shows average cardinality vs bytes used, 100

 * samples per cardinality (when the set was not representable because

 * of registers with too big value, the dense representation size was used

 * as a sample).

 *

 * 100 267

 * 200 485

 * 300 678

 * 400 859

 * 500 1033

 * 600 1205

 * 700 1375

 * 800 1544

 * 900 1713

 * 1000 1882

 * 2000 3480

 * 3000 4879

 * 4000 6089

 * 5000 7138

 * 6000 8042

 * 7000 8823

 * 8000 9500

 * 9000 10088

 * 10000 10591

 *

 * the dense representation uses 12288 bytes, so there is a big win up to

 * a cardinality of ~2000-3000. for bigger cardinalities the constant times

 * involved in updating the sparse representation is not justified by the

 * memory savings. the exact maximum length of the sparse representation

 * when this implementation switches to the dense representation is

 * configured via the define server.hll_sparse_max_bytes.

 */

网站地图