5 种基础数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
这5种数据结构是直接提供给用户使用的,是数据的保存形式,其底层实现主要依赖这8种数据结构:简单动态字符串(SDS)、LinkedList(双向链表)、Hash Table(哈希表)、SkipList(跳跃表)、Intset(整数集合)、ZipList(压缩列表)、QuickList(快速列表)。
Redis基本数据结构的底层数据结构实现如下:
String |
List |
Hash |
Set |
Zset |
SDS |
LinkedList/ZipList/QuickList |
Hash Table、ZipList |
ZipList、Intset |
ZipList、SkipList |
Redis 3.2之前,List底层实现是LinkedList或者ZipList。Redis 3.2之后,引入了LinkedList和ZipList的结合QuickList,List的底层实现变为QuickList。
String:
String是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的base64编码或者解码或者图片的路径)、序列化后的对象。
Redis 并没有使用C的字符串表示,而是自己构建了一种简单动态字符串(Simple Dynamic String,SDS)。相比于C的原生字符串,Redis的SDS不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C字符串为 O(N)),除此之外,Redis的SDS API是安全的,不会造成缓冲区溢出。
命令介绍
SET key value 设置指定 key 的值
SETNX key value 只有在 key 不存在时设置 key 的值
GET key 获取指定 key 的值
MSET key1 value1 key2 value2 … 设置一个或多个指定 key 的值
MGET key1 key2 ... 获取一个或多个指定 key 的值
STRLEN key 返回 key 所储存的字符串值的长度
INCR key 将 key 中储存的数字值增一
DECR key 将 key 中储存的数字值减一
EXISTS key 判断指定 key 是否存在DEL key(通用)删除指定的
keyEXPIRE key seconds(通用) 给指定 key 设置过期时间
基本操作:
> SET key value OK > GET key "value" > EXISTS key (integer) 1 > STRLEN key (integer) 5 > DEL key |
批量设置 :
> SET key value OK > GET key "value" > EXISTS key (integer) 1 > STRLEN key (integer) 5 > DEL key |
计数器(字符串内容为整数时可以使用):
> SET number 1 OK > INCR number #将key中储存的数字值增一 (integer) 2 > GET number "2" > DECR number #将key中储存的数字值减一 (integer) 1 > GET number "1" |
设置过期时间(默认为永不过期):
> EXPIRE key 60 (integer) 1 > SETNX key 60 value #设置值并设置过期时间 OK > TTL key (integer) 56 |
String应用场景:
需要存储常规数据的场景
举例:缓存session、token、图片地址、序列化后的对象(相较于Hash存储更节省内存)。
相关命令:SET、GET。
需要计数的场景
举例:用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
相关命令:SET、GET、INCR、DECR。
分布式锁
利用SETNX key value命令可以实现一个最简易的分布式锁(存在一些缺陷,通常不建议这样实现分布式锁,建议用redisson)。
List:
Redis中的List其实就是链表数据结构的实现。
Redis的List的实现为一个双向链表(double linkedlist),即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
命令介绍
RPUSH key value1 value2 ... 在指定列表的尾部(右边)添加一个或多个元素
LPUSH key value1 value2 ... 在指定列表的头部(左边)添加一个或多个元素
LSET key index value 将指定列表索引 index 位置的值设置为 value
LPOP key 移除并获取指定列表的第一个元素(最左边)
RPOP key 移除并获取指定列表的最后一个元素(最右边)
LLEN key 获取列表元素数量
LRANGE key start end 获取列表 start 和 end 之间 的元素
通过LRANGE命令,你可以基于List实现分页查询,性能非常高!
List应用场景:
信息流展示
举例:最新文章、最新动态。
相关命令:LPUSH、LRANGE。
消息队列
Redis List数据结构可以用来做消息队列,只是功能过于简单且存在很多缺陷,不建议。
相对来说,Redis 5.0新增加的一个数据结构Stream更适合做消息队列一些,只是功能依然非常简陋。和专业的消息队列相比,还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。
Hash:
Redis中的Hash是一个String类型的field-value(键值对)的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
Hash类似于JDK1.8前的HashMap,内部实现也差不多(数组+链表)。不过,Redis的 Hash 做了更多优化。
命令介绍
HSET key field value 设置指定哈希表中指定字段的值
HSETNX key field value 只有指定字段不存在时设置指定字段的值
HMSET key field1 value1 field2 value2 ... 同时将一或多个field-value (域值)对设置到指定哈希表(适合存对象)
HGET key field 获取指定哈希表中指定字段的值
HMGET key field1 field2 ... 获取指定哈希表中一个或者多个指定字段的值
HGETALL key 获取指定哈希表中所有的键值对
HEXISTS key field 查看指定哈希表中指定的字段是否存在
HDEL key field1 field2 ... 删除一个或多个哈希表字段
HLEN key 获取指定哈希表中字段的数量
HINCRBY key field increment 对指定哈希中的指定字段做运算操作(increment是正数为加,负数为减)
模拟对象存储:key field1 value1 field2 value2
> HMSET userInfoKey name "guide" description "dev" age 24 OK > HEXISTS userInfoKey name # 查看key对应的value中指定的字段是否存在 (integer) 1 > HGET userInfoKey name # 获取存储在哈希表中指定字段的值 "guide" > HGET userInfoKey age "24" > HGETALL userInfoKey # 获取在哈希表中指定key的所有字段和值 1) "name" 2) "guide" 3) "description" 4) "dev" 5) "age" 6) "24" > HSET userInfoKey name "GuideGeGe" > HGET userInfoKey name "GuideGeGe" > HINCRBY userInfoKey age 2 (integer) 26 //(24+2) |
Hash应用场景:
对象数据存储场景
举例:用户信息、商品信息、文章信息、购物车信息。
相关命令:HSET (设置单个字段的值)、HMSET(设置多个字段的值)、HGET(获取单个字段的值)、HMGET(获取多个字段的值)。
Set(集合):
Redis中的Set类型是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于Java中的HashSet。当你需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择,并且Set提供了判断某个元素是否在一个Set集合内的重要接口,这个也是List 所不能提供的。
可以基于Set轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
命令介绍
SADD key member1 member2 ... 向指定集合添加一个或多个元素
SMEMBERS key 获取指定集合中的所有元素
SCARD key 获取指定集合的元素数量
SISMEMBER key member 判断指定元素是否在指定集合中
SINTER key1 key2 ... 获取给定所有集合的交集
SINTERSTORE destination key1 key2 ... 将给定所有集合的交集存储在destination中
SUNION key1 key2 ... 获取给定所有集合的并集
SUNIONSTORE destination key1 key2 ... 将给定所有集合的并集存储在destination中
SDIFF key1 key2 ... 获取给定所有集合的差集
SDIFFSTORE destination key1 key2 ... 将给定所有集合的差集存储在destination中
SPOP key count 随机移除并获取指定集合中一个或多个元素
SRANDMEMBER key count 随机获取指定集合中指定数量的元素
基本操作:
> SADD mySet value1 value2 (integer) 2 > SADD mySet value1 # 不允许有重复元素,因此添加失败 (integer) 0 > SMEMBERS mySet 1) "value1" 2) "value2" > SCARD mySet (integer) 2 > SISMEMBER mySet value1 (integer) 1 > SADD mySet2 value2 value3 (integer) 2 |
求交集:
> SINTERSTORE mySet3 mySet mySet2 #将myset和myset2的交集存到myset3中 (integer) 1 > SMEMBERS mySet3 1) "value2" |
求并集:
> SUNION mySet mySet2 1) "value3" 2) "value2" 3) "value1" |
求差集:
> SDIFF mySet mySet2 #差集是由所有属于mySet但不属于A的元素组成的集合 1) "value1" |
应用场景:
需要存放的数据不能重复的场景(唯一)
举例:网站UV统计(数据量巨大的场景还是HyperLogLog(基数统计)更适合一些)、文章点赞、动态点赞等场景。
相关命令:SCARD(获取集合数量)。
网站流量统计之UV(Unique Visitor):独立访客,将每个独立上网电脑(以cookie为依据)视为一位访客,一天之内(00:00-24:00),访问您网站的访客数量。一天之内相同cookie的访问只被计算1次。 网站流量统计之PV(Page View):访问量,即页面浏览量或者点击量,用户每次对网站的访问均被记录1次。用户对同一页面的多次访问,访问量值累计 网站流量统计之IP:指独立IP数。00:00-24:00内相同IP地址只被计算一次,做网站优化的朋友最关心这个。 网站流量统计之人均PV:指选择时间范围内,每个访客访问网站的PV数,网站优化的终极目标! |
需要获取多个数据源交集、并集和差集的场景(判断元素是否在某个集合)
举例:共同好友(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集)等场景。
相关命令:SINTER(交集)、SINTERSTORE(交集)、SUNION(并集)、SUNIONSTORE(并集)、SDIFF(差集)、SDIFFSTORE(差集)。
需要随机获取数据源中的元素的场景(随机取值)
举例 :抽奖系统、随机。
相关命令:SPOP(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、SRANDMEMBER(随机获取集合中的元素,适合允许重复中奖的场景)。
Sorted Set(有序集合):
和Set相比,Sorted Set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,还可以通过score的范围来获取元素的列表。有点像是Java中HashMap和 TreeSet(TreeSet底层是二叉树,可以对对象元素进行排序)的结合体。
命令介绍
ZADD key score1 member1 score2 member2 ... 向指定有序集合添加一个或多个元素
ZCARD KEY 获取指定有序集合的元素数量
ZSCORE key member 获取指定有序集合中指定元素的score值
ZINTERSTORE destination numkeys key1 key2 ... 将给定所有有序集合的交集存储在destination 中,对相同元素对应的score值进行SUM聚合操作,numkeys为集合数量
ZUNIONSTORE destination numkeys key1 key2 ... 求并集,其它和ZINTERSTORE类似
ZDIFF destination numkeys key1 key2 ... 求差集,其它和 ZINTERSTORE 类似
ZRANGE key start end 获取指定有序集合 start 和 end 之间的元素(score 从低到高)
ZREVRANGE key start end 获取指定有序集合 start 和 end 之间的元素(score 从高到底)
ZREVRANK key member 获取指定有序集合中指定元素的排名(1个)(score 从高到底排序)
基本操作:
> ZADD myZset 2.0 value1 1.0 value2 (integer) 2 > ZCARD myZset 2 > ZSCORE myZset value1 2.0 > ZRANGE myZset 0 1 1) "value2" 2) "value1" > ZREVRANGE myZset 0 1 1) "value1" 2) "value2" > ZADD myZset2 4.0 value2 3.0 value3 (integer) 2 |
获取指定元素排名:
> ZREVRANK myZset value1 0 > ZREVRANK myZset value2 1 |
求交集:
> ZINTERSTORE myZset3 2 myZset myZset2 1 > ZRANGE myZset3 0 1 WITHSCORES value2 5//score 值进行 SUM 聚合操作(1+4) |
求并集:
> ZUNIONSTORE myZset4 2 myZset myZset2 3 > ZRANGE myZset4 0 2 WITHSCORES value1 2 value3 3 value2 5 |
求差集:
> ZDIFF 2 myZset myZset2 WITHSCORES value1 2 |
Sorted set应用场景:(有序!!!)
需要随机获取数据源中的元素根据某个权重进行排序的场景
举例:各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
相关命令:ZRANGE (从小到大排序) 、 ZREVRANGE (从大到小排序)、ZREVRANK (指定元素排名)。
需要存储的数据有优先级或者重要程度的场景
举例 :优先级任务队列。
相关命令 :ZRANGE (从小到大排序) 、 ZREVRANGE (从大到小排序)、ZREVRANK (指定元素排名)。
String还是Hash存储对象数据更好呢?
String存储的是序列化后的对象数据,存放的是整个对象。Hash是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash就非常适合。
String存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String就非常适合。
在绝大部分情况,我们建议使用 String 来存储对象数据即可!
3 种特殊数据结构:HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
Bitmap:
Bitmap存储的是连续的二进制数字(0和1),通过Bitmap, 只需要一个bit位来表示某个元素对应的值或者状态,key就是对应元素本身。我们知道8个bit可以组成一个byte,所以 Bitmap本身会极大的节省储存空间。
可以将Bitmap看作是一个存储二进制数字(0和1)的数组,数组中每个元素的下标叫做offset(偏移量)。
命令介绍
SETBIT key offset value 设置指定 offset 位置的值
GETBIT key offset 获取指定 offset 位置的值
BITCOUNT key start end 获取start和end之前值为1的元素个数
BITOP operation destkey key1 key2 ... 对一个或多个Bitmap进行运算,可用运算符有AND, OR, XOR以及NOT。
基本操作:
# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位 > SETBIT mykey 7 1 (integer) 0 > SETBIT mykey 7 0 //第7位覆盖位0 (integer) 1 > GETBIT mykey 7 (integer) 0 > SETBIT mykey 6 1 (integer) 0 > SETBIT mykey 8 1 (integer) 0 # 通过 bitcount 统计被被设置为 1 的位的数量。 > BITCOUNT mykey (integer) 2 |
Bitmap应用场景:
需要保存状态信息(0/1 即可表示)的场景(是/否)
举例 :用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
相关命令 :SETBIT、GETBIT、BITCOUNT、BITOP。
HyperLogLog:
HyperLogLog是一种有名的基数计数概率算法 ,基于LogLog Counting(LLC)优化改进得来,并不是Redis特有的,Redis只是实现了这个算法并提供了一些开箱即用的 API。
Redis提供的HyperLogLog占用空间非常非常小,只需要12k的空间就能存储接近2^64个不同元素。并且,Redis对HyperLogLog的存储结构做了优化,采用两种方式计数:
稀疏矩阵:计数较少的时候,占用空间很小。
稠密矩阵:计数达到某个阈值的时候,占用12k的空间。
基数计数概率算法为了节省内存并不会直接存储元数据,而是通过一定的概率统计方法预估基数值(集合中包含元素的个数)。因此,HyperLogLog的计数结果并不是一个精确值,存在一定的误差(标准误差为 0.81%)。
HyperLogLog 的使用非常简单,但原理非常复杂。
命令介绍
PFADD key element1 element2 ... 添加一个或多个元素到HyperLogLog中
PFCOUNT key1 key2 获取一个或者多个HyperLogLog的唯一计数
PFMERGE destkey sourcekey1 sourcekey2 ... 将多个HyperLogLog合并到destkey中,destkey会结合多个源,算出对应的唯一计数。
基本操作:
> PFADD hll foo bar zap (integer) 1 > PFADD hll zap zap zap (integer) 0 > PFADD hll foo bar (integer) 0 > PFCOUNT hll (integer) 3 > PFADD some-other-hll 1 2 3 //some-other-hll是key 设置element 1 2 3 (integer) 1 > PFCOUNT hll some-other-hll //统计两个key下的元素数 (integer) 6 > PFMERGE desthll hll some-other-hll //合并成deshll "OK" > PFCOUNT desthll (integer) 6 |
HyperLogLog应用场景:
数量量巨大(百万、千万级别以上)的计数场景
举例:热门网站每日/每周/每月访问ip数统计、热门帖子uv统计
相关命令:PFADD、PFCOUNT。
Geospatial index:
Geospatial index(地理空间索引,简称 GEO)主要用于存储地理位置信息,基于Sorted Set实现。
通过GEO我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。
命令介绍
GEOADD key longitude1 latitude1 member1 ... 添加一或多个元素对应的经纬度信息到GEO中
GEOPOS key member1 member2 ... 返回给定元素的经纬度信息
GEODIST key member1 member2 M/KM/FT/MI 返回两个给定元素之间的距离
GEORADIUS key longitude latitude radius distance 获取指定位置附近distance范围内的其他元素,支持 ASC(由近到远)、DESC(由远到近)、Count(数量) 等参数
GEORADIUSBYMEMBER key member radius distance 类似于 GEORADIUS命令,只是参照的中心点是GEO中的元素
基本操作:
> GEOADD personLocation 116.33 39.89 user1 116.34 39.90 user2 116.35 39.88 user3 3 > GEOPOS personLocation user1 116.3299986720085144 39.89000061669732844 > GEODIST personLocation user1 user2 km //user1和user2之间的距离 1.4018 |
GEO中存储的地理位置信息的经纬度数据通过GeoHash算法转换成了一个整数,这个整数作为Sorted Set的score(权重参数)使用。
获取指定位置范围内的其他元素 :
> GEORADIUS personLocation 116.33 39.87 3 km //116.33 39.87这个位置3km以内的元素 user3 user1 > GEORADIUS personLocation 116.33 39.87 2 km > GEORADIUS personLocation 116.33 39.87 5 km user3 user1 user2 >GEORADIUSBYMEMBER personLocation user1 5 km //user1这个位置5km以内的元素 user3 user1 user2 > GEORADIUSBYMEMBER personLocation user1 2 km user1 user2 |
移除元素 :
GEO 底层是 Sorted Set ,你可以对 GEO 使用 Sorted Set 相关的命令。
> ZREM personLocation user1 1 > ZRANGE personLocation 0 -1 user3 user2 > ZSCORE personLocation user2 //经纬度数据的转换结果 4069879562983946 |
Geospatial Index应用场景:
需要管理使用地理空间数据的场景
举例:附近的人。
相关命令: GEOADD、GEORADIUS、GEORADIUSBYMEMBER 。
String的底层实现是什么?
SDS,Redis并没有使用C原生的字符串,而是自己编写了简单动态字符串SDS,除了可以存文本数据,还可以存二进制数据,除此之外,相较于C的字符串,SDS获取字符串长度的时间复杂度是O(1),SDS的API是二进制安全的,不会出现缓冲区溢出的现象。
SDS共有五种(redis 3.2之后)实现方式 SDS_TYPE_5(并未用到)、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64,其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
对于后四种实现都包含了下面这4个属性:
len :字符串的长度也就是已经使用的字节数
alloc:总共可用的字符空间大小,alloc-len 就是 SDS 剩余的空间大小
buf[] :实际存储字符串的数组
flags :低三位保存类型标志
SDS 相比于 C 语言中的字符串有如下提升:二缓len
可以避免缓冲区溢出 :(扩容)C 语言中的字符串被修改(比如拼接)时,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。SDS 被修改时,会先根据 len 属性检查空间大小是否满足要求,如果不满足,则先扩展至所需大小再进行修改操作。
获取字符串长度的复杂度较低 : C 语言中的字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。SDS 的长度获取直接读取 len 属性即可,时间复杂度为 O(1)。
减少内存分配次数 :(多给点,少丢点) 为了避免修改(增加/减少)字符串时,每次都需要重新分配内存(C 语言的字符串是这样的),SDS 实现了空间预分配和惰性空间释放两种优化策略。当 SDS 需要增加字符串时,Redis 会为 SDS 分配好内存,并且根据特定的算法分配多余的内存,这样可以减少连续执行字符串增长操作所需的内存重分配次数。当 SDS 需要减少字符串时,这部分内存不会立即被回收,会被记录下来,等待后续使用(支持手动释放,有对应的 API)。
二进制安全 :C 语言中的字符串以空字符 \0 作为字符串结束的标识,这存在一些问题,像一些二进制文件(比如图片、视频、音频)就可能包括空字符,C 字符串无法正确保存。SDS 使用 len 属性判断字符串是否结束,不存在这个问题。
购物车信息用 String 还是 Hash 存储更好呢?
由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:(用户key买了xx商品field)
用户 id 为 key
商品 id 为 field,商品数量为 value
那用户购物车信息的维护具体应该怎么操作呢?
用户添加商品就是往 Hash 里面增加新的 field 与 value;
查询购物车信息就是遍历对应的 Hash;
更改商品数量直接修改对应的 value 值(直接 set 或者做运算皆可);
删除商品就是删除 Hash 中对应的 field;
清空购物车直接删除对应的 key 即可。
使用 Redis 实现一个排行榜怎么做?
Redis中有一个叫做sorted set的数据结构经常被用在各种排行榜的场景,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
相关的一些Redis命令: ZRANGE (从小到大排序) 、ZREVRANGE(从大到小排序)、ZREVRANK (指定元素排名)。
使用 Set 实现抽奖系统需要用到什么命令?
SPOP key count :随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。
SRANDMEMBER key count : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
标签:元素,Redis,指定,获取,key,integer,数据结构 From: https://www.cnblogs.com/cjhtxdy/p/17679289.html