5种Redis数据结构详解
2.1.1 全局命令
1 查看所有键 key*
2 键总数 dbsize (dbsize命令在计算键总数的时候不会遍历所有键,而是直接获取Redis内置的键总数变量,时间复杂度为O(1),而keys命令会遍历所有键,时间复杂度为O(n),当Redis保存了大量键时,线上环境禁止使用)
3 检查键是否存在 exists key 存在返回1,不存在返回0
4 删除键 del key 返回成功删除键的个数,不存在的返回0
5 键过期 expire key seconds ttl 命令会返回剩余过期时间 -1 键没设置过期时间 -2 键不存在
6 键的数据类型结构 type key 返回类型,不存在返回none
2.1.2 数据结构和内部编码
每种数据结构都有自己的底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码
每种数据结构都有两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist两种内部编码,可以通过object encoding命令查询内部编码
Redis这样设计有两个好处:第一:可以改进内部编码,而对外的数据结构和命令没有影响。第二 多种内部编码实现可以在不同的场景下发挥各自的优势。比如,ziplist比较节省内存,但是列表元素比较多的情况下,性能有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist
2.1.3 单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务
1 引出单线程模型
调用客户端的过程:发送命令,执行命令,返回结果
所有的命令在一个队列里排队等待被执行,不存在多个命令被同时执行的情况
2 为什么单线程还能跑这么快
第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时间长约100纳秒,这是Redis达到每秒万级别访问的重要基础
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间
第三 单线程避免了线程切换和竟态产生的消耗
单线程带来几个好处:第一,单线程简化数据结构和算法的实现。第二,单线程避免了线程切换和竟态产生的消耗。但是对于每个命令的执行命令是有要求的,如果某个命令执行时间过长,就会造成其他命令的阻塞,Redis是面向快速执行场景的数据库,单线程是理解Redis的核心
2.2 字符串
Redis的字符串类型是其他几种的基础,值可以是字符串(简单,复杂的json,xml),数字(整型,浮点),二进制(图片,音频,视频),最大值不能超过512MB
2.2.1 命令
1 常用命令
1 设置值 set key value 秒级过期时间 毫秒级过期时间 nx|xx
setnx setxx同上
应用场景:由于Redis是单线程命令处理机制,如果多个客户端同时执行setnx key value,根据特性,只有一个客户端能设置成功,可以作为分布式锁的一种实现方案
2 获取值 get key 不存在返回nil
3 批量设置值 mset key value
4 批量获取值 mget key
学会使用批量操作,有助于提高业务处理效率,但要注意每次批量操作所发送的命令不是无节制的,数量过多造成Redis阻塞或网络拥塞
5 计数 incr key
返回结果有三种情况
值不是整数 返回错误
值是整数,返回自增后的结果
键不存在,按照值为0自增,返回结果为1
还有decr(自减),incrby(自增指定数字),decrby(自减指定数字),incrbyfloat(自增浮点数)
2 不常用命令
1 追加值 append key value
2 字符串长度 strlen key
3 设置并返回原值 getset key value
4 设定指定位置的字符 setrange key offset value
5 获取部分字符串 getrange key start end
2.2.2 内部编码
字符串内部编码有3种:int 8个字节的长整型 embstr 小于等于39个字节的字符串 raw 大于39个字节的字符串。Redis会根据当前值的类型和长度决定使用哪种内部编码实现
2.2.3 典型使用场景
1 缓存功能
Redis作为缓存层,Mysql作为存储层,绝大部分的请求的数据都是从Redis中获取。由于Redis具有支持并发的特性,所以缓存通常能起到加速读写和降低后段压力的作用
开发提示:键名命名方式:业务名:对象名:id:[属性]作为键名
伪代码实现:
UserInfo getUserInfo(long id){ userRedisKey="user:info:"+id value=redis.get(userRedisKey); UserInfo userInfo; if(value!=null){ userInfo=deserialize(value) }else{ userInfo=mysql.get(id) if(userInfo!=null) redis.setex(userRedisKey,3600,serizelize(userInfo)) }
return userInfo
}
2 计数
long incrVideoCounter(long id){ key="video:playCount:"+id; return redis.incr(key) }
开发提示:防作弊,按照不同维度计数,数据持久化到底层数据源
3 共享Session
4 限速
phoneNum="13800000000"; key="shortMsg:limit:"+phoneNum; isExists=redis.set(key,1,"EX 60",NX); if(isExists !=null ||redis.incr(key)<=5){ 通过 }else{ 限速 }
某一些网站限制一个ip地址不能在一秒钟之内访问超过n次也可以采用类似的思路
2.3 哈希
哈希类型是指键值本身又是一个键值对结构
2.3.1 命令
1 设置值
hset key field value
2 获取值 hget key field
3 删除field hdel key field
4 计算field的个数 hlen key
5 批量设置或获取field-value hmget key field hmset key field value
6 判断field是否存在 hexists key field
7 获取所有field hkeys key
8 获取所有的value hvals key
9 获取所有的field-value hgetall key
开发提示:如果一定要获取全部的field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型
10 hincrby hincrby float
11 计算value的字符串长度 hstrlen key field
2.3.2 内部编码
内部编码有两种:
ziplist(压缩列表) 哈希元素个数<hash-max-ziplist-entries,所有值<hash-max-ziplist-value配置时,Redis会使用ziplist作为hash的内部实现,ziplist使用更加紧凑的结构实现多个元素存储,节省内存方面比hashtable更加优秀
hashtable(哈希表) 当hash类型无法满足ziplist 条件时,选择,因为hashtable的读写时间度为O(1)
2.3.3 使用场景
UserInfo getUserInfo(long id){ userRedisKey="user:info:"+id; userInfoMap=redis.hgetAll(userRedisKey); userInfoMap userInfo; if(userInfoMap!=null){ userInfo=transferMapToUserInfo(userInfoMap); }else{ userInfo=mysql.get(id); redis.hmset(userRedisKey,tranferUserInfoToMap(userInfo)); redis.expire(userRedisKey,3600); } return userInfo; }
哈希类型和关系型数据库两点不同:
1 哈希类型是稀疏的,而关系型数据库是完全结构化的
2 关系型数据库可以做复杂的查询,而Redis去模拟关系型复杂查询开发困难,维护成本高
三种方法缓存用户信息
1 原声字符串类型:每个属性一个键
优点:简单直观,每个属性都支持更新操作
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以一般不会在生产环境用
2 序列化字符串类型:将用户信息序列化后用一个键保存
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率
缺点:序列化和反序列化有一定的开销,同时每次更新属性,都需要把数据取出来反序列化,更新后再序列化到Redis中
3 哈希类型:每个用户属性使用一对field-value,但是只用一个键保存
优点:简单直观,如果使用合理,可以减少内存空间的使用
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多的内存
2.4 列表
列表类型用来存储多个有序的字符串,一个列表最多存储2的32次方-1个元素,列表是一种比较灵活的数据结构,它可以灵活的充当栈和队列的角色,在实际开发上有很多应用场景
列表有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。第二、列表中的元素可以是重复的
2.4.1 命令
1 添加操作
1.1 从右边往左插入元素 rpush key value
1.2 从左往右插入元素 lpush key value
1.3 向某个元素前或者后插入元素 linsert key before|after pivot value
2 查找
1 获取指定范围内的元素列表 lrange key start end
索引下标有两个特点:第一,索引下标从左到右分别是0-n-1,从右到左是-1--n,第二,lrange的end选项包含了自身,这个和很多编程语言不包含end不太相同
2 获取列表指定索引下标的元素 lindex key index
3 获取列表长度 llen key
3 删除
1 从列表左侧弹出元素 lpop key
2 从列表右侧弹出 rpop key
3 删除指定元素 lrem key count value
4 按照索引范围修剪列表 ltrim key start end
4 修改
修改指定索引下标的元素 lset key index newValue
5 阻塞操作 brpop blpop key timeout
1 列表为空:如果timeout=3,那么客户端要等到3s后返回,如果timeout=0,客户端则阻塞等下去,如果添加了数据,客户端立刻返回
2 列表不为空:客户端立即返回
3
2.4.2 内部编码
列表类型的内部编码有两种
ziplist(压缩列表):当列表元素个数<list-max-ziplist-entries,同时list-max-ziplist-value(64字节),Redis会选用列表的内部实现来减少内存的使用
linkedlist(链表) 当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现
2.4.3 使用场景
1 消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列
2 文章列表
两个问题:第一,如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时考虑使用pipeline批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。第二,分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围元素的性能会变差,此时可以考虑二级拆分
开发提示:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpsh+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
2.5 集合
集合用来保存多个字符串元素,和列表不同的是不允许有重复元素,并且集合中元素是无序的
2.5.1 命令
1 集合内操作
1.1 添加元素 sadd key element
1.2 删除元素 srem key element
1.3 计算元素个数 scard key
1.4 判断元素是否在集合中 sismember key element
1.5 随机从集合返回指定个数元素 srandmember key
1.6 从集合随机弹出元素 spop key
1.7 获取所有元素 smembers key
2 集合间操作
1 求多个集合的交集 sinter key ...
2 求多个集合的并集 suinon key..
3 求多个集合的差集 sdiff key ..
4 将交集、差集、并集结果保存
sinterstore destination key
sdiffstore destination key
suionstore destionation key
2.5.2 内部编码
集合类型的内部有两种:
intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合内部的实现,从而减少内存的使用
hashtable(哈希表) 当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现
2.5.3 使用场景
集合类型比较典型的应用场景是标签。
1 给用户添加标签
sadd user:1:tags tag1 tag2
2 给标签添加用户
sadd tag1:users user:1 user:3
开发提示:用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致
3 删除用户下的标签
srem user:1:tags tag1 tag5
4 删除标签下的用户
srem tag1:users user:1
5 计算用户共同感兴趣的标签
sinter user:1 tags user:2 tags
开发提示:sadd=Tagging(标签) spop/srandmember=Random item(生成随机数,比如抽奖)
spop/srandmember=Random item(生成随机数,比如抽奖) sadd+sinter=Social Graph(社交需求)
2.6 有序集合
有序集合就是在集合之上加了个score作为排序的依据
2.6.1 命令
1 集合内
1添加成员 zadd key score memeber
nx xx ch 返回此次操作后,有序集合元素和分数发生变化的个数,incr:对score做增加
有序集合相比集合提供了排序字段,但是也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)
2 计算成员个数
scard key
3 计算某个成员的分数 zscore key member
4 计算成员的排名 zrank key member
5 删除成员 zrem key member
6 增加成员的分数 zincrby key increment member
7 返回指定排名范围的成员 zrange key start end
8 返回指定分数范围的成员 zrangebysore key min max
9 返回指定分数范围成员个数 zcount key min max
10 删除指定排名内的升序元素 zremrangebyrank key start end
11 删除指定分数范围的成员 zremrangebyscore key min max
2 集合间的操作
1 交集 zinterstore destination numkeys key
2 并集 zunionstore destionation numkeys key
2.6.2 内部编码
有序集合类型的内部编码有两种:
ziplist(压缩列表) 当有序集合的元素个数小于zset-max-ziplist-entries配置,同时每个元素的值都小于zset-max-ziplist-value配置时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效的减少内存的使用
skiplist(跳跃表) 当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因此此时ziplist的读写效率会下降
2.6.3 使用场景
有序集合比较典型的使用场景是排行榜系统。例如视频网站需要对用户上传的视频做排行榜榜
1 添加用户赞数 zdd user:ranking:2016_03_15 mike 3
之后 zincrby user:ranking:2016_03_15 mike 1
2 取消用户赞数
zrem user:rank:2016_03_15 mike
3 展示获取赞数最多的十个用户
zrevrangebyrank user:ranking:2016_03_15 0 9
4 展示用户信息以及用户分数
此功能可以将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用zcore和zrank两个功能
2.7 键管理
2.7.1 单个键管理
1 键重命名 rename key newkey
2 随机返回一个键 randomkey
3 键过期 -1 键没有设置过期时间 -2 键不存在
expire key seconds:键在seconds秒后过期
expire key itmestamp 键在秒级时间戳timestamp后过期
1 如果expire key的键不存在,返回结果为0
2 如果过期时间为负值,键会立即被删除,就如使用使用del命令一样
3 persist 命令可以将键的过期时间清除
4 对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易在开发中被忽视
5 Redis不支持二级数据结构内部元素的过期功能,例如不能这列表类型的一个元素做过期时间设置
6 setex命令作为set+expire组合,不但原子执行,同时减少了网络通讯的时间
4 迁移键
迁移键功能非常重要,有move、dump+restore、migrate三组迁移键的方法,它们的实现方式以及使用场景不太相同
1 move 用于在Redis内部进行数据迁移
2 dump+restore 实现在不同的Redis实例之间进行数据迁移的功能,这个迁移分两步
1 在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式
2 在目标Redis上,restore命令将上面的序列化的值进行复原,其中ttl参数代表过期时间
3 migrate 命令用于在Redis实例间进行数据迁移的
2.7.2 遍历键
Redis提供了两个命令遍历所有的键分别是keys和scan
1 全量遍历键 keys pattern
* 代表匹配任意字符
.代表匹配一个字符
[] 代表匹配一个字符
\x用来转义,例如要匹配星号,问号需要进行转义
大量容易造成阻塞
2 渐进式遍历
scan可以想象成只扫描一个字典中的一部分键,直到将字典中所有键遍历完毕
对应的命令还有hsan、sscan、zcan
渐进式遍历可以有效解决keys命令可能产生的阻塞问题,在有增删的时候,新来的键无法保证遍历到
2.7.3 数据库管理
1 切换数据库 select dbIndex
2 flushdb/flushall 用于清除数据库 数据多的时候会出现阻塞
2.8 本章终点回顾
相关推荐:
以上就是 5种Redis数据结构详解的详细内容,更多请关注其它相关文章!