1. String类型
其基本的编码方式是RAW
,是基于SDS
实现的,存储上限为512MB
此时字符串的数据和存储
redisObject
的内存区域是一块区域,因此在这种情况下需要两次内存分配申请,那么同样的,回收内存也需要两次的内存回收,将数据和对象头连在一起,可以更好的利用CPU的cache
如果存储的SDS
长度小于44
个字节,那么就会采用EMBSTR
编码,此时的object head
与SDS
是一段连续的空间,申请内存的时候只需要调用一次内存分配函数,效率是更高的。
EMBSTR编码方式有什么缺点?
如果字符串的长度增加需要重新分配内存的时候,整个redisObject
和sds
都需要重新分配空间,所以embstr
编码的字符串对象实际上是只读的,redis
没有为embstr
编码的字符串对象编写任何相关的修改程序,当我们对embstr
编码的字符串对象执行任何修改命令时,程序会先将embstr
转换为raw
,然后在执行修改
如果存储的字符串是整数值
,并且大小在LONG_MAX
范围内,那么就会采用INT
类型的编码,直接将数据保存在RedisObject
的ptr
指针的位置刚好8个字节
,不再需要SDS
了
分布式锁的实现
set命令
有一个nx
参数可以实现只有key
不存在才可以插入,可以基于它来实现分布式锁
- 如果key不存在,那么就显示插入成功,表示加锁成功
- 如果key存在,那么就显示插入失败
2. List类型
List的底层数据结构是由双向链表
或者ZipList
实现的
- 如果列表的元素个数小于
512
个,列表中的每个元素的值都小于64
个字节,Redis会使用压缩列表
作为List
类型的底层数据结构 - 如果列表的元素不满足上述的条件,
Redis
会使用双向链表作为List
类型的底层数据结构
在3.2之后,List数据类型底层数据结构就只由
quickList
进行实现了,替代了双向链表和压缩列表
常用的命令有
# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
LPUSH key value [value....]
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value....]
# 移除并返回key列表的头元素
LPOP key
# 移除并返回key列表的尾元素
RPOP key
# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LRANGE key start stop
# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout
3. Hash类型
Hash
是一个键值对,value = [{f1,v1},{f2,v2},...,{fn,vn}]
Hash
的底层实现是基于压缩列表
或者是哈希表实现的
- 如果
Hash
类型元素的个数是小于512
个字节的(默认值)
,那么就可以由hash-max-ziplist-entries
进行配置,所有值小于64
字节的话,那么Redis
会基于压缩列表
来作为Hash
类型的底层数据结构 - 如果哈希类型元素不满足上述的条件,
Redis
就会使用哈希表
来作为Hash
类型的底层数据结构
# 存储一个哈希表key的键值
HSET key filed value
# 获取哈希表key对应的field的键值
HGET key filed
# 在一个哈希表key中存储多个键值对
HMSET key field value [field value
# 批量获取哈希表key中的多个field键值
HMGET key field [field ...]
# 删除哈希表key中的field键值
HDEL key field [field ...]
# 返回哈希表key中field的数量
HLEN key
# 返回哈希表key中所有的键值
HGETALL key
# 为哈希表key中field键的值加上增量n
HINCRBY key field n
4. SET类型
set
类型是一个无序并且唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储
一个集合最多可以存储2^32-1
个元素,其内部的实现是基于哈希表或者整数集合实现的
- 如果集合中的元素都是整数而且元素的个数是小于
512
个,那么Redis
就会使用整数集合作为Set
类型的底层数据结构 - 如果集合中的元素不满足上述的条件,那么
Redis
就会使用哈希表作为Set
类型的底层数据结构
Set
的常用操作
# 往集合key中存入元素,元素存在则忽略,如果key不存在那么就新建
SADD key member [member...]
# 从集合key中删除元素
SREM key member [member...]
# 获取集合key中的所所有元素
SMEMBERS key
# 获取集合key中元素个数
SCARD key
# 判断member元素是否存在于集合key中
SISMEMBER key member
# 从集合key中随机选出count个元素,元素不删除,可以拿来做抽奖
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]
# 交集运算
SINTER key [key...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]
# 并集运算
SUNION key [key]
# 将并集运算结果存入到新集合中
SUNIONSTORE destination key [key ...]
# 差集运算
SDIFF key [key...]
# 将差集运算结果存入新集合中
SDIFFSTORE destination key [key ...]
5. ZSET类型
Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。
有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
- 如果有序集合的元素个数小于
128
个,并且每个元素的值小于64
字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构; - 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
Zset
的常用操作:
# 往有序集合key中加入带分值的元素
ZADD key score member [[score member]]
# 往有序集合key中删除元素
ZREM key member [member...]
# 返回有序集合key中元素member的分支
ZSCORE key member
# 返回有序集合key中元素的个数
ZCARD key
# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member
# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]
# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]
# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...]
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...]
6. BITMAP类型
BitMap
也就是位图,是一连串的二进制数组,可以通过偏移量offset
来定位元素,BitMap
通过最小的单位bit
来进行0|1
的设置,表示某个元素的值或者状态,时间复杂度为O(1)
特别适合一些二值统计的场景,BitMap
本身使用String
类型作为底层数据结构实现的一种统计二值状态的数据类型,String
类型由于底层是SDS
,其API在处理的时候会将字符以二进制的形式
进行解析,因此完全就可以利用这些个特性来表示一个二值的状态
# 设置值,其中value只能是1或者0
SETBIT key offset value
# 获取值
GETBIT key offset
# 获取指定范围内值为1的个数
# start和end以字节为单位
BITCOUNT key start end
如何判断一个用户是否登录?
只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT
判断对应的用户是否在线。 5000 万用户只需要 6 MB 的空间。
假如我们要判断 ID = 10086 的用户的登陆情况:
第一步,执行以下指令,表示用户已登录。
SETBIT login_status 10086 1
第二步,检查该用户是否登陆,返回值 1 表示已登录。
GETBIT login_status 10086
第三步,登出,将 offset 对应的 value 设置成 0。
SETBIT login_status 10086 0
7. HyperLogLog类型
Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。
所以,简单来说 HyperLogLog 提供不精确的去重计数。
HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64
个不同元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。
这什么概念?举个例子给大家对比一下。
用 Java 语言来说,一般 long 类型占用 8 字节,而 1 字节有 8 位,即:1 byte = 8 bit,即 long 数据类型最大可以表示的数是:2^63-1
。对应上面的2^64
个数,假设此时有2^63-1
这么多个数,从 0 ~ 2^63-1
,按照long
以及1k = 1024 字节
的规则来计算内存总数,就是:((2^63-1) * 8/1024)K
,这是很庞大的一个数,存储空间远远超过12K
,而 HyperLogLog
却可以用 12K
就能统计完。
如何统计网页的UV数?
可以使用Redis
中提供的HyperLogLog
数据结构,具体实现流程如下:
将访问页面的每个用户都添加到HyperLogLog
中
PFADD ad:uv user2 user1 user3 user4 ...
如何就可以使用PFCOUNT
直接获取UV值了
PFCOUNT ad:uv
8. GEO类型
GEO
是基于ZSET
进行实现的,它在使用的时候,将当前的经纬度通过GEOHASH()
的一个计算,得到一个得分,然后加入到ZSET
中,这其中的两个关键机制就是对二维地图做区间划分
和对区间进行编码
,一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为ZSET
元素的权重分数
这样一来,就可以把经纬度保存到ZSET
中,利用ZSET
提供的按照权重进行有序范围查询的特性,实现LBS
服务中频繁使用的搜索附近
的需求
# 存储指定的地理空间位置,可以将一个或者多个经度、维度、位置名称添加到指定的ZSET
GEOADD key longitude latitude member [...]
# 从给定的key中返回所有指定名称的位置
GEOPOS key member
# 返回两个给定位置间的距离(单位)
GEODIST key member1 member2 [m|km|ft|mi]
# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
怎么实现一个滴滴打车?
假设车辆 ID 是 33,经纬度位置是(116.034579,39.030452),我们可以用一个 GEO 集合保存所有车辆的经纬度,集合 key 是 cars:locations。
执行下面的这个命令,就可以把 ID 号为 33 的车辆的当前经纬度位置存入 GEO 集合中:
GEOADD cars:locations 116.034579 39.030452 33
当用户想要寻找自己附近的网约车时,LBS 应用就可以使用 GEORADIUS 命令。
例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息(116.054579,39.030452 ),查找以这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。
GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10