本文主要介绍Redis高级数据结构,包括Bitmap(位图)、Hyperloglog、GEO以及使用场景。

1. Bitmap

1. 概述

BitMap,即位图,也就是 byte 数组,用二进制表示,只有 0 和 1 两个数字,底层使用SDS存储。

2. API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#对key所存储的字符串值,获取指定偏移量上的位(bit)
getbit key offset

# 对key所存储的字符串值,设置或清除指定偏移量上的位(bit) 1. 返回值为该位在setbit之前的值 2. value只能取0或1 3. offset从0开始,即使原位图只能10位,offset可以取1000
setbit key offset value

#获取位图指定范围中位值为1的个数 如果不指定start与end,则取所有
bitcount key [start end]

#做多个BitMap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destKey中
bitop op destKey key1 [key2...]

#计算位图指定范围第一个偏移量对应的的值等于targetBit的位置 1. 找不到返回-1 2. start与end没有设置,则取全部 3. targetBit只能取0或者1
bitpos key tartgetBit [start end]

3. 使用场景

有的场景用Bitmap会有奇效,比如:

日活跃用户

如果是日活跃用户的话只需要创建一个Bitmap即可,每个用户根据ID对应Bitmap中的一位,当某个用户满足活跃用户条件后,就在Bitmap中把标识此用户的位置为1。

假设有1000W用户,那么只需要1000W/8/1024/1024 差不多1.2M的空间,简直完美

那周活跃用户、月活跃用户呢?

同理,多建几个Bitmap即可。

同样的,每日签到用户在线状态什么的也可以这么实现。

4. 实例

假设用来统计日活跃用户。

用户满足条件则写入缓存,假设用户ID1满足活跃条件了,那么:

1
2
#setbit key offset value 这里直接把id当做offset就更方便了
setbit key 1 value

周活跃用户则创建7个Bitmap,最后统计一周7天每天都活跃的用户呢?

直接把7个Bitmap进行运算求交集,最后还为1的就是7天都活跃的,毕竟7个1与之后才能为1。

1
2
#bitop op destKey key1 [key2...] 大概是这样的
bitop and destKey Monday [Tuesday...]

如果是7天中登录过的(1天及其以上)那就是求并集,运算。

5. bloomfilter

还可以用来实现布隆过滤(在官方提供布隆过滤之前,一般都是用的Bitmap实现),具体逻辑如下:

  • 1.根据hash算法确定key要映射到哪些bit上(一般为多个,越多冲突越小)
  • 2.setbit 将对应的bit全置为1
  • 3.查询时同样先hash,如果对应的映射不是都为1则说明该key一定不存在,都为1则说明可能存在。

2. Hyperloglog

1. 概述

HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

  • 基数不大,数据量不大就用不上,会有点大材小用浪费空间
  • 有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
  • 和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmap 方便很多
  • 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数

2. API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#添加指定元素到 HyperLogLog 中
#影响基数估值则返回1否则返回0
PFADD key element [element ...]

#返回给定 HyperLogLog 的基数估算值。
#白话就叫做去重值 带有 0.81% 标准错误(standard error)的近似值
PFCOUNT key [key ...]

#将多个 HyperLogLog 合并为一个 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...]

3. 使用场景

统计 APP或网页 的一个页面,每天有多少用户点击进入的次数。同一个用户的反复点击进入记为 1 次。

1
2
// 判定当前元素是否存在
直接添加PFADD 如果影响基数估值则返回1否则返回0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func RedisHyperLogLog() {
	var (
		key    = "clickStatic"
		userId = 10010
	)
	// 删除旧测试数据
	rc.Del(key)
	for i := 10000; i < 10010; i++ {
		rc.PFAdd(key, i)
	}
	// 判定当前元素是否存在
	// PFAdd添加后对基数值产生影响则返回1 否则返回0
	res := rc.PFAdd(key, userId)
	if err := res.Err(); err != nil {
		logrus.Errorf("err :%v", )
		return
	}
	if res.Val() != 1 {
		logrus.Println("该用户已统计")
	} else {
		logrus.Println("该用户未统计")
	}
}

3. GEO

支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。

暂时没用到,一般用的也不是很多,到时候在写。

//TODO