Redis五种数据结构原理及应用场景

前言

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件; 下面就了解的应用场景及其原理做个总结:

String类型

介绍

存储字符串类型的key-value

常用命令及应用场景

常见命令 命令介绍 应用场景
set/get/setex 存储字符串类型的key-value 验证码、幂等性(重复提交判断)、token令牌、 序列化json对象存储 set user:captcha:zhangsan 123456 ex 30/setex user:captcha:zhangsan 30 123456 (张三收到验证码为123456,30s后失效),set user:zhangsan 用户信息json串(用户张三的个人信息)
mset/mget 批量设置key-value 同set/get mset user:name zhangsan user:age 20 (用户姓名为张三,年龄20)
incr 对key对应的值进行加1操作,并返回新值 统计、计数器、发号器 incr article:123:reading(对id为123的文章阅读量增加1)
incrby 对key对应的值进行加increment操作,并返回新值 同incr incr article:123:reading 10(对id为123的文章阅读量增加10)
setnx key如果存在则返回0,不存在则返回1 分布式锁 setnx addr 123(设置地址为123)
getset 设置key的值,并返回key的旧值 状态变更(比如用户下单,可以修改下单状态) getset addr 567(注意:返回为旧值123)

原理

Redis的字符串是动态字符串,是可以修改的字符串,它的内部表示就是一个字符数组,内部结构的实现类似于Java的ArrayList,它的内部结构是一个带长度信息的字节数组

注意

  • 值的长度不能超过512MB
  • key的命名规范,不要过长,冒号分割, 业务名:表名:ID

List

介绍

  • 字符串列表,按照插入顺序排序

常用命令及应用场景

常见命令 命令介绍 应用场景
lpush/rpush 将一个或多个值插入到列表头部(左边)/(右边) 最新评论列表/非实时榜单(定时计算榜单,如手机日销榜单) lpush phone:rank:daily iphone13 xiaomi12 huaweiP50 (手机日销榜 第一华为,第二小米12,第三苹果)
lpop/rpop 移除并获取列表最左边/右边一个元素 - rpop phone:rank:daily (移除key phone:rank:daily最右边一个元素)
llen 获取列表长度 - llen phone:rank:daily(获取key phone:rank:daily的列表长度)
lindex 通过索引获取列表中元素 - lindex phone:rank:daily 0 (获取第一个元素)
lrange 获取key对应的list指定下标范围的元素(-1表示所有元素) - lrange phone:rank:daily 0 -1 (获取列表所有元素)
brpop 移除并获取列表最右边一个元素(如果没有元素则阻塞) 简单队列 brpop phone:rank:daily 30(监听key phone:rank:daily最右边一个元素,监听时间30S)
lrem 移除元素指定个数 - lrem phone:rank:daily 1 iphone13(phone:rank:daily队列移除一个iphone13元素)

原理

  • 双向链表,插入删除时间复杂度O(1)快,查找为O(n)慢

注意

  • 通常添加一个元素到列表的头部(左边)或者尾部(右边)。
  • 存储的都是String字符串类型。
  • 支持分页操作,高并发项目中,第一页数据都是来源list,第二页和更多信息则是通过数据库加载。
  • 一个列表最多可以包含232-1个元素(最好不要超过1K)。

Hash

介绍

  • 一个String类型的filed和value的映射表,hash特别适合用于存储对象。

常用命令及应用场景

常见命令 命令介绍 应用场景
hset/hget/hgetall 设置/得到 key指定的哈希集中指定字段值 用户个人信息、商品详情 hset product:detail:1 title iphone13(设置id为1的商品详情 其中标题为iPhone13)
hmset/hmget 批量设置/得到 key指定的哈希集中指定字段值 用户个人信息、商品详情 hset product:detail:1 title iphone13 price 6999 stock 10(设置id为1的商品详情 其中标题为iPhone13,价格为6999,库存为10件)
hincrby 增加key指定的hash集中指定字段数值 购物车增加或减少 hincrby product:detail:1 stock 1(id为1的商品增加一件库存)
hdel 从key指定的hash集中移除指定域 - hdel product:detail:1 stock(移除id为1的商品的库存信息)
hexists 返回hash里边field是否存在(存在是1,不存在未0) - hexists product:detail:1 stock (id为1的商品库存信息是否存在)

原理

  • 哈希对象的编码有两种,分别是:ziplist(见下文补充)、hashtable。

  • 当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用ziplist(压缩列表)存储;否则使用 hashtable 存储。

如何解决hash冲突

  • Redis 采用了渐进式 rehash 策略.这也是 hash 中最重要的部分。
  • redis在扩容的时候执行 rehash 策略会保留新旧两个 hashtable 结构,查询时也会同时查询两个 hashtable.Redis会将旧 hashtable 中的内容一点一点的迁移到新的 hashtable 中,当迁移完成时,就会用新的 hashtable 取代之前的.当 hashtable 移除了最后一个元素之后,这个数据结构将会被删除。
  • 正常情况下,当 hashtable 中元素的个数等于数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍.如果 Redis 正在做 bgsave(持久化) 时,可能不会去扩容,因为要减少内存页的过多分离(Copy On Write).但是如果 hashtable 已经非常满了,元素的个数达到了数组长度的 5 倍时,Redis 会强制扩容。
  • 当hashtable 中元素逐渐变少时,Redis 会进行缩容来减少空间占用,并且缩容不会受 bgsave 的影响,缩容条件是元素个数少于数组长度的 10%。

注意

  • 每个hash可以存储232 -1键值对(40多亿)

Set

介绍

将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略

常用命令及应用场景

常见命令 命令介绍 应用场景
sadd 添加一个或多个元素到集合key中,如果已经在集合key中则忽略 去重、社交应用关注、粉丝、用户标签 sadd user:1:tags:hangzhou(id为1的用户来自杭州)
scard 返回集合存储key的基数 粉丝数量、统计网站PV、UV、IP数 scard user:1:fans 用户id为1的粉丝数量为多少
sdiff 返回两个集合的差集 商品推荐 sdiff user:1:tags user:2:tags(用户id为1的与用户id为2的标签差集)
sinter 返回指定所有集合的成员交集 共同好友、共同关注 sinter user:1:tags user:2:tags(用户id为1的与用户id为2的标签交集)
sismember 判断是否是集合中成员 - sismember user:1:tags:hangzhou (user:1:tags中是否存在hangzhou标签)
srem 移除key集合指定元素 - srem user:1:tags:hangzhou(移除user:1:tags中标签hangzhou)
sunion 返回多个集合的并集 - sunion user:1:tags user:2:tags(用户id为1的与用户id为2的标签并集)

原理

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

注意

  • 集合是通过哈希表来实现的

SortedSet

介绍

  • 用于将一个或多个元素及其分数值加入到有序集当中。
  • 如果某个成员已存在,那么更新这个分值。

常用命令及应用场景

常见命令 命令介绍 应用场景
zadd 向有序集合添加一个或多个成员 实时排行榜(商品热销榜、积分榜) zadd shop:rank 90 iphone 80 huawei(商品排行榜iPhone第一,华为第二)
zcard 获取有序集合的成员数 榜单数量 zcard shop:rank(获取集合shop:rank数量)
zcount 计算有序集合中指定区间分数的成员数 统计数量 zcount shop:rank 80 90(返回销量为80-90的品牌数)
zincrby 有序集合中对指定成员的分数加上增量 实时增加销量 zincrby shop:rank 10 huawei(华为手机销量增加10)
zrange/zrevrange 通过索引区间返回有序集合指定区间内的成员,分数值递增(从小到大)/(从大到小) 榜单排名 zrange shop:rank 0 -1(返回商品榜单)
zrevrank/zrank 返回集合中成员排第几(从大到小)/(从小到大) 榜单具体排名情况 zrevrank shop:rank iphone
zrem 删除集合中一个或多个元素 - zrem shop:rank iphone huawei(移除商品排行榜中苹果和华为)
zscore 返回集合中成员分数值 查看具体某个商品积分 zscore shop:rank iphone(查看iPhone手机销量)

原理

  • 底层使用Ziplist压缩列表和"跳跃表"两种存储结构

注意

如果重复添加相同的数据,score的值将被反复覆盖,保留最后一次修改的结果。


补充

跳跃表简单介绍

举例:
比如找78
正常 1-> 4->7->13->16->25->57->78 8次
跳跃表如图 1->7->16->57->79(发现比78小,又没找到;往下一层走)->78 6次
如果数据量大速度提升则更明显
Redis五种数据结构原理及应用场景_第1张图片

压缩表简单介绍

举例:
我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是20个字节)。存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。
Redis五种数据结构原理及应用场景_第2张图片

数组的优势占用一片连续的空间可以很好的利用CPU缓存访问数据。如果我们想要保留这种优势,又想节省存储空间我们可以对数组进行压缩。

Redis五种数据结构原理及应用场景_第3张图片

但是这样有一个问题,我们在遍历它的时候由于不知道每个元素的大小是多少,因此也就无法计算出下一个节点的具体位置。这个时候我们可以给每个节点增加一个lenght的属性。

Redis五种数据结构原理及应用场景_第4张图片
如此。我们在遍历节点的之后就知道每个节点的长度(占用内存的大小),就可以很容易计算出下一个节点再内存中的位置。这种结构就像一个简单的压缩列表了。

你可能感兴趣的:(Redis,redis,数据结构,中间件)