04、Redis三种特殊数据类型之Bitmaps、HyperLogLog、Geospatial

一、Bitmaps

计算机二进制

计算机要处理的信息是多种多样的,如数字、文字、符号、图形、音频、视频等,这些信息在人们的眼里是不同的。但对于计算机来说,它们在内存中都是一样的,都是以二进制的形式来表示。

计算机是以二进制的形式来存储数据的,它只认识 0 和 1 两个数字,我们在屏幕上看到的文字,在存储之前都被转换成了二进制(0和1序列),在显示时也要根据二进制找到对应的字符。

例如“abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图:
 

Bitmaps简介

许多开发语言都提供了操作位功能,合理地使用位能够有效地提高内存使用率和开发效率。Redis提供了Bitmaps这个“数据结构”,可以实现对位的操作。把数据结构加上引号主要因为:

1、 Bitmaps本身不是一种数据结构,实际上它就是字符串,但它可以对字符串的位进行操作;
2、 Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太一样可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量;
 

常用命令

SETBIT key offset value:设置或者清空key的value(字符串)在offset处的bit值。

那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串大到在offset处有bit值。参数offset需要大于等于0,并且小于232(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。

警告:当set最后一个bit(offset等于232-1)并且key还没有一个字符串value或者其value是个比较小的字符串时,Redis需要立即分配所有内存,这有可能会导致服务阻塞一会。在一台2010MacBook Pro上,offset为232-1(分配512MB)需要~300ms,offset为230-1(分配128MB)需要~80ms,offset为228-1(分配32MB)需要~30ms,offset为226-1(分配8MB)需要8ms。注意,一旦第一次内存分配完,后面对同一个key调用SETBIT就不会预先得到内存分配。

GETBIT key offset:返回key对应的string在offset处的bit值 当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。到内存分配。

BITCOUNT key [start end]:统计字符串被设置为1的bit数.

一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。

start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。

不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0。

BITOP operation destkey key [key …]:bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。

# 创建一个Bitmaps,在偏移量5的位置设置值为1
setbit bitkey 5 1
# 查看当前bitkey偏移量5位置的值
getbit bitkey 5
# 统计偏移量0-5位置,值为1的个数
bitcount bitkey 0 5
# 查看bitkey 和bitkey2交际,并将结果保存在dest中
bitop and dest bitkey bitkey2

二、HyperLogLog

基数

在数学上,基数或势,即集合中包含的不重复元素的“个数”。比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素个数)为5。

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。

但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。

解决基数问题有很多种方案:

1、 数据存储在MySQL表中,使用distinctcount计算不重复个数;
2、 使用Redis提供的hash、set、bitmaps等数据结构来处理;

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

HyperLogLog解决基数问题

HyperLogLog是Redis的高级数据结构,是统计基数的利器。

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

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

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

常用命令

PFADD key element [element …]:添加指定元素到 HyperLogLog 中。

PFCOUNT key [key …]: 返回给定 HyperLogLog 的基数估算值。

PFMERGE destkey sourcekey [sourcekey …]:将多个 HyperLogLog 合并为一个 HyperLogLog

# 向hll 中添加多个元素
pfadd hll java php java go scala go
# 统计hll的基数
pfcount hll
# 把hll、hll2合并到destkey 中
pfmerge destkey hll hll2

HyperLogLog数据结构

HyperLogLog实际上不会存储每个元素的值,它使用的是概率算法,通过存储元素的hash值的第一个1的位置,来计算元素数量。

三、Geospatial

Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

常用命令

GEOADD key longitude latitude member [longitude latitude member …]
将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。

该命令以采用标准格式的参数x,y,所以经度必须在纬度之前。这些坐标的限制是可以被编入索引的,区域面积可以很接近极点但是不能索引。具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

GEOPOS key member [member …]:返回一个或多个位置的经纬度信息,由于采用了geohash算法,返回的经纬度和添加时的数据可能会有细小误差。

GEODIST key member1 member2 [unit]:回一个key中指定两个位置之间的距离,unit可以指定长度单位:m,km,ft等 默认为m。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]: 以给定位置为中心,半径不超过给定半径的附近所有位置。

# 添加北京和上海的地理位置到cities中
GEOADD cities 116.404269 39.91582 "beijing" 121.478799 31.235456 "shanghai"
# 查看北京的经纬度
geopos cities beijing
# 查看北京和上海的距离
geodist cities beijing shanghai km
# 查看120 30位置,500KM内的城市
georadius cities 120 30 500 km