一、简述
前面一篇文章中,我们已经介绍过了《Redis基本数据类型》,这些基本数据类型对于大多数的业务需求都可以很好的满足,但是对于这些基本数据类型,在一些特殊的场景中,却并不一定适用,下面我们来介绍一下Redis提供的一些扩展数据类型和时序数据库模块。
bitmap:基于bit位的存储,每一个bit存储0或1,一般用来进行海量数据的精准判重
HyperLogLog:一般用来对海量数据进行基于概率的基数统计,比如说网站的独立访客数、独立IP数等
Geo:基于地理空间的数据存储,常应用在那些基于位置服务,也就是我们常说的LBS(Location-Based Service)的应用,比如说打车等生活服务类应用
Stream:消息流,Redis自5.0版本之后,引入了消息队列的机制,也就是我们熟悉的Pub/Sub(发布/订阅)机制。
RedisTimeSeries:时间数据库模块,主要存储一些跟时间戳相关,需要范围查询,聚合计算等场景的数据集
二、Bitmap
Bitmap,也叫位图,通过二进制bit中的0或1来表示,某个位置的标记,因为每一个bit都只能是0或1,所以通常用来表示一个数据(通常对应于数组下标)是否已经存在(存在的话,该bit位上是1,否则为0)。下面,我们看看具体的存储结构,如下图所示:
其实在Redis中,bitmap是通常字符串来实现的,每一个字符的ASC2编码值代表一个字节,比如上面的数据就代表字符串"ab"。
Redis中的bitmap最多能存512M个字节,如果超出会报错,但这在绝大多数场景中已经足够了,因为512M可以表示:512*1024*1024*8 = 4294967296个。
bitmap一般用在什么场景呢&#xff1f;比如说&#xff0c;我们有一个id(id <4294967296)集合A&#xff0c;要存储起来&#xff0c;然后和另外一个id集合B进行比较&#xff0c;找出重复的id有哪些&#xff1f;比如说有1亿个id吧。那么这种情况&#xff0c;采用bitmap就十分合适了。
bitmap的getbit和setbit的时间复杂度都是&#xff1a;O(1)
三、HyperLogLog
上面简述中&#xff0c;我们已对提到&#xff0c;HyperLogLog主要是用来做基数统计的&#xff0c;我们知道在计算机领域&#xff0c;可以做基数统计的还有其它的类型&#xff0c;比如set, hash, bitmap都可以实现&#xff0c;但是面对海量数据的基数统计&#xff0c;这些数据结构都将消耗大量内存&#xff0c;比如说set和hash可以适用少量数据的统计和滤重&#xff0c;bitmap适合海量数据的滤重&#xff0c;但不适合海量数据的统计。
其实HyperLogLog呢&#xff0c;它的底层也是采用bitmap来实现的&#xff0c;它是采用一种基数估算算法来实现的&#xff0c;如果你有兴趣&#xff0c;可以去研究一下基数估算算法。HyperLogLog&#xff0c;它仅仅只需要12KB的内存空间&#xff0c;可以完成对264个数据完成基数统计&#xff0c;而且标准误差只有0.81%。
采用PFADD命令往HyperLogLog中添加数据&#xff0c; PFCOUNT命令就可以返回HyperLogLog的基数统计结果。
添加与统计的时间复杂度都是&#xff1a;O(1)
四、Geo
基于LBS的应用&#xff0c;比如说生活服务类的应用&#xff0c;我们想要查询附近3公里内所有的餐馆&#xff0c;那么这个时候&#xff0c;我们输入的数据应该就是一个经纬度坐标&#xff0c;那么Redis就会从数据库中查找出以输入坐标为圆心3公里作为半径范围内所有数据。
那么Redis是存储经纬度坐标的呢&#xff1f;
其实对于地理空间数据的编码方式&#xff0c;通常来说有两种&#xff1a;
(1)、二分区间法&#xff1b;
(2)、坐标转换法&#xff1b;
关于上述两种算法的实现原理&#xff0c;如果你有兴趣&#xff0c;可以自行了解一下。
Redis采用的是性能更高的坐标转换法&#xff0c;基本思想就是将经纬度二维坐标数据转为整数存储。
最常见的命令有&#xff1a;
geoadd&#xff1a;输入坐标&#xff0c;将一个经纬度坐标添加到geo数据集中
georadius&#xff1a;输入坐标和半径&#xff0c;返回geo数据集中&#xff0c;所有在指定坐标为圆心指定半径范围内的所有其它坐标点。
geodist&#xff1a;输入两个坐标点&#xff0c;计算两点之间的距离。
五、Stream
Redis自5.0.0之后&#xff0c;引入了消息队列的功能&#xff0c;也就是开始支持Pub/Sub(发布/订阅)的功能。
Stream&#xff0c;消息流的底层存储结构采用的是listpack&#xff0c;就是一个序列化之后的字符串列表。因为它还是一个队列&#xff0c;那么它需要满足先进先出的特性。
Redis的消息队列相对于传统专门的消息中间件来说&#xff0c;有哪些优点和缺点&#xff1a;
优点&#xff1a;
(1)、高性能&#xff1b;
(2)、低延迟&#xff1b;
(3)、轻量级。
缺点&#xff1a;
(1)、容量小&#xff0c;基于内存&#xff0c;如果内存爆了&#xff0c;就无法写入新消息&#xff1b;
(2)、可靠性差&#xff0c;需要自己业务代码实现可靠性保证&#xff1b;
综合来看&#xff1a;如果业务对消息队列的容量要求不是那么大、可靠性要求也不是非常非常高&#xff0c;同时又不想引入专门的消息中间件使得架构变得更加复杂的话&#xff0c;那么Redis的消息队列功能也不失为一种办法。
六、RedisTimeSeries
RedisTimeSeries是Redis提供的时序数据库模块&#xff0c;那么时序数据库主要用来干什么呢&#xff1f;
比如说有这样的需求&#xff1a;
我们要存储北京市最近一年每一分钟的温度数据&#xff0c;然后查询需求有&#xff1a;
(1)、输入一个时间点&#xff0c;查询这个时间点的温度数据&#xff1b;
(2)、输入一个时间范围&#xff0c;同时返回这个时间范围的温度平均值&#xff0c;最高值&#xff0c;最低值。
如果采用之前的基本数据类型来存储&#xff0c;那么可能需要同时使用hash和sorted set。而且&#xff0c;要求一个时间范围的温度平均值的话&#xff0c;我们还必须把数据加载到客户端来进行聚合计算。
这里需要说明一下&#xff0c;RedisTimeSeries跟上面的扩展数据类型不一样&#xff0c;它并不是Redis内置的功能&#xff0c;它只是一个Redis扩展模块。它专门面向时间序列数据提供了数据类型和访问接口&#xff0c;并且支持在 Redis 实例上直接对数据进行按时间范围的聚合计算。
所以&#xff0c;你在使用RedisTimeSeries时&#xff0c;需要先把它的源码单独编译成动态链接库 redistimeseries.so&#xff0c;再使用 loadmodule 命令进行加载&#xff0c;如下所示&#xff1a;
loadmodule redistimeseries.so
常见的操作如下&#xff1a;
(1)、用 TS.CREATE 命令创建时间序列数据集合&#xff1b;
(2)、用 TS.ADD 命令插入数据&#xff1b;
(3)、用 TS.GET 命令读取最新数据&#xff1b;
(4)、用 TS.MGET 命令按标签过滤查询数据集合&#xff1b;
(5)、用 TS.RANGE 支持聚合计算的范围查询。