- 预备
- 字符串(string)
- 哈希(hash)
- 命令
- 设置值:hset key field value
- 获取值:hget key field
- 删除field:hdel key field [field...]
- 计算field个数:hlen key
- 批量设置或获取field-value:hmset key filed value [filed1 value1 ...]、hmget key field [field...]
- 判断field是否存在:hexists key field
- 获取所有field:hkeys key
- 获取所有value:hvals key
- 获取所有的field-value:hgetall key
- hincrby hincrbyfloat
- 计算value的字符串长度:hstrlen key field
- 内部编码
- 使用场景
- 缓存用户信息的三种方法
- 命令
- 列表(list)
- 集合(set)
- 有序集合(zset)
- 有序集合内命令
- 添加成员:zadd key score member [socre1 member1...]
- 计算成员个数:zcard key
- 计算某个成员的分数:zscore key member
- 计算成员的排名:zrank|zrevrank key member
- 删除成员:zrem key member [member1...]
- 增加成员的分数:zincrby key increment member
- 返回指定排名范围内的成员:zrange|zrevrange key start end [withscores]
- 返回指定score分数范围的成员:zrangebyscore key min max[withscores] [limit offset count]、zrevrangebyscore key max min[withscores] [limit offset count]
- 返回指定分数范围成员个数:zcount key min max
- 删除指定排名内的升序元素:zremrangebyrank key start end
- 删除指定分数范围内的成员:zremrangebyscore key min max
- 有序集合间命令
- 内部编码
- 使用场景
- 有序集合内命令
- 键管理
预备
Redis的全局命令、数据结构和内部编码、单线程命令处理机制
全局命令
Redis有5种数据结构,他们是K-V中的V,有一些通用的命令
查看所有键:keys *
会遍历所有键,所以时间复杂度为O(n),当Redis保存了大量键时,线上环境禁止使用
键总数:dbsize
不会遍历所有键,直接获取Redis内置的键总数变量,所以复杂度为O(1)
检查键是否存在:exists key
如果存在则返回1,不存在返回0
删除键:del key [key ...]
可删除一个或多个键,返回的结果为成功删除的键的个数
键过期:expire key seconds ttl key
-
expire key seconds:设置过期时间
对键添加过期时间,超过过期时间后会自动删除键 -
ttl key:查看剩余过期时间
返回值大于0则显示剩余的秒数
-1则没有设置过期时间
-2表示键不存在
键的数据结构类型:type key
如果键不存在,则返回none
数据结构和内部编码
type命令返回的是当前键的数据结构类型,分别是string、hash、list、set、zset,且每种数据结构都有自己底层的内部编码实现
- 好处:
1.方便改进内部编码,而对外的数据结构和命令没有影响,例如Redis 3.2增加的quicklist
2.多种内部编码实现在不同场景下发挥各自的优势
查询内部编码:object encoding key
当键不存在时返回nil表示无
Redis 3.2 为单线程架构
Redis 6 为多线程I/O,需要手动开启,Redis 4 开始引入多线程概念
Redis 3.2 为单线程架构和I/O多路复用模型来实现高性能的内存数据库服务
单线程模型
Redis是单线程来处理命令的,所以一条命令从客户端达到度无端不会立刻被执行,所有命令都会进入一个队列中,然后逐个执行。不会有多个命令同时执行,不会产生并发问题,这就是Redis单线程的基本模型
为什么单线程还能这么快
有三点:
- 纯内存访问
所有数据存放在内存中,内存的相应市场大约为100ns,这是Redis速度快最重要基础 - 非阻塞I/O
使用epoll作为I/O多路复用技术的实现,和事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多时间 - 单线程
单线程避免了线程切换和线程竞争产生的消耗
单线程的好处与问题
-
好处:
1.简化数据结构和算法实现,并发数据结构实现较麻烦
2.避免线程切换和竟态产生消耗,锁和线程切换 -
坏处:
阻塞是致命的,如果某个命令执行过长,造成其他命令的阻塞,所以Redis是面向快速执行场景的数据库
字符串(string)
是Redis最基础的数据结构,其他集中数据结构都是在字符串类型的基础上构建的。
Redis的键都是字符串类型
数据结构为字符串的值
即上图中的string的值
最大不得超过512MB,字符串的值可以为:
- 简单的字符串
- 复杂的字符串:JSON、XML
- 数字:整数、浮点数
- 二进制:图片、音频、视频
常用命令
设置值:set key value [ex seconds] [px milliseconds] [nx|xx]
- ex seconds:设置秒级过期时间,可连写setex
- px milliseconds:设置毫秒级过期时间
- nx:键必须不存在,才可以设置成功,用于添加,可连写setnx
- xx:键必须存在,才可以设置成功,用于更新
setnx的作用:分布式锁的实现方案
Redis是单线程处理,所以多个客户端同时执行setnx key value时,由于只有一个客户端可以设置成功,setnx可以作为分布式锁的实现方案之一,该官方网页介绍如下
https://redis.io/topics/distlock
获取值:get key
如果不存在,返回nil(空)
批量设置值:mset key value [key1 value1 key2 value2 ...]
批量获取值:mget key key1 key2 ...
如果key不存在则返回nil
批量操作意义:提高开发效率
如果没有批量操作则执行n次需要n次网络时间,而批量操作则只需要1次网络时间。Redis支持每秒数万的读写操作,但这指的是Redis服务端的处理能力,客户端还需要网络时间延迟。因为Redis的处理能力非常高,所以网络会称为性能瓶颈。每次批量操作发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞
计数:incr key
对值作自增操作+1,有三种情况
- 不是整数,返回错误
- 是整数,返回+1后的结果
- key不存在,按照0,第一次执行为0+1=1
不常用命令
追加值:append key value
向字符串尾部追加值
字符串长度:strlen key
返回字符串长度
设置并返回原值:getset key value
设置值,并返回key对应的原来值
设置指定位置的字符:setrange key offeset value
设置对应key的值的下表位置的值(下标从0开始)
获取部分字符串:getrange key start end
获取对应key的下标为start和end的字符(下标从0开始)
内部编码
字符串类型的内部编码有3种,Redis会根据当前值的类型和长度决定使用哪种内部编码实现
- int:8个字节的长整型
- embstr:≤39个字节的字符串
- raw:>39个字节的字符串
典型使用场景
缓存功能
Redis作为缓存层,MySQL作为存储层,大部分请求数据从Redis获取,加速读写,降低后端压力的作用。
例如客户访问过程:
- 1.获取用户的基础信息
- 2.首先从Redis获取用户信息
- 3.如果没有从Redis获取到用户信息,则从MySQL种获取,并将结果回写到Redis种,添加过期时间
计数
实现快速计数、查询缓存的功能,数据可以异步落地到其他数据源
例如视频播放数:
long incrVideoCounter(long id){
key= "video:playCount:"+id;
return redis.incr(key);
}
共享session
分布式Web服务将用户的Session信息(例如用户登陆信息)放入Redis进行集中管理,只要保证Redis是高可用和扩展性的,每次用户更新或者查询登陆信息都直接从Redis种集中获取
限制
使用Redis对安全辅助的限制
例如验证码10分钟最多获取3次
phoneNum ="13812345678"
key="shortMsg:"+phoneNum
//设置该key不存在时存入,时长10分钟
isExists=redis.set(key,1,"EX 600","NX");
if(isExists !=null|| redis.incr(<=3)){
//验证通过继续执行
}else{
//验证不通过
}
其他又如限制IP等
哈希(hash)
在Redis中,hash类型是指键值本身有时一个键值对结构
如:value={{field1,value1}{field2,value2}{field3,value3}...}
命令
设置值:hset key field value
设置成功返回1,失败0,hsetnx 类似setnx,只是作用域变为field
获取值:hget key field
如果键或field不存在返回nil
删除field:hdel key field [field...]
删除一个或多个field,返回成功删除的个数
计算field个数:hlen key
返回key 对应的feild个数,如果不存在返回0
批量设置或获取field-value:hmset key filed value [filed1 value1 ...]、hmget key field [field...]
判断field是否存在:hexists key field
存在返回1,不存在返回0
获取所有field:hkeys key
返回hash键对应的所有field
获取所有value:hvals key
获取所有的field-value:hgetall key
如果hash元素个数比较多,会存在阻塞Redis的可能
hincrby hincrbyfloat
计算value的字符串长度:hstrlen key field
内部编码
ziplist(压缩列表)
使用更加紧凑的结构实现多个元素的连续存储,比hashtable更节省内存
当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,使用ziplist作为hash的内部实现
hashtable(哈希表)
读写时间复杂度O(1)
当ziplist类型无法满足hash类型时,使用hashtable作为hash内部实现(ziplist此时读写效率会下降)
使用场景
缓存数据,例如用户信息
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在操作上会更加便捷
- hash类型和关系型数据库不同处
1.hash类型是稀疏的,而关系型数据库是完全结构化。即hash每个key都可以由不同的field,数据库只要有列必须赋值,即使为null
2.关系型数据库可以做复杂查询,Redis模拟关系型复杂查询开发困难,维护成本高
缓存用户信息的三种方法
1.原生字符串类型:每个属性一个key
优点:简单直观,每个属性都支持更新操作
缺点:占用key多,内存占用量大,用户信息内聚性比较差
2.序列化字符串类型:将用户属性信息序列化后用一个key保存
优点:简化编程,合理地使用序列化可以提高内存效率
缺点:序列化和反序列化有开销,每次更新时都需要把数据全部取出进行反序列化,更新后再序列化到Redis中
3.hash类型:每个用户属性使用一对 field-value,但只用一个key保存
优点:简单直观,如果使用合理可以减少内存空间使用
缺点:要控制hash在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存
列表(list)
至多存储2^32 -1个有序的字符串。列表中的每个string称为元素(element)。在Redis中可以对list列表两端插入(push)和弹出(pop),还可以取指定范围的元素列表、获取指定索引下标的元素(下标从0开始)。可以充当栈和队列的角色
list特点:
- 有序,即可以通过索引下标获取元素
- 可重复:可包含两个相同的字符串
添加命令
从右边插入元素:rpush key value [valee1 ...]
从左到右遍历元素:lrang key 0 -1
从左边插入元素 lpush key value [valee1 ...]
向某个元素前/后插入元素:insert key before|after pivot(需要比较的元素element) value
从列表中找到等于piovt的元素,在前/后插入一个新的元素value,并返回操作后的列表长度
注意上图始终与元素相同时,以下标小为准
查找命令
获取指定范围内的元素列表:lrange key start end
获取索引为 start 和end 的范围元素
获取列表指定索引下标的元素:lindex key index
获取列表长度:llen key
删除命令
从列表左侧弹出元素:lpop key
返回弹出的元素的值
从列表右侧弹出:rpop key
删除指定元素:lrem key count value
从列表中找到等于value的元素进行删除,根据count不同
- count>0,从左到右,删除最多count个元素
- count<0,从右到左,删除最多count绝对值个元素
- count=0,删除所有
按照索引范围修建列表:ltrim key start end
保留下标为star到end的元素
修改
修改指定索引下标的元素:lset key index newValue
修改索引下标为index的值为newValue
阻塞操作:blpop key [key ...] timeout、brpop key [key ...] timeout
- blpop和brpop是lpop和rpop的阻塞版本
- key表示多个列表的键
- timeout为阻塞时间
1.如果列表为空,则等待timeout秒后返回,若timeout=0则一直阻塞等待下去
2.如果列表不为空,客户端会立即返回
内部编码
三种类型
ziplist(压缩列表)
列表中的元素个数小于512个且每个元素值小于64字节,Redis选用ziplist来作为列表的内部实现,减少内存的使用
linkedlist
ziplist无法满足时使用
quicklist
以ziplist为节点的linkedlist
使用场景
消息队列
lpush+brpoop组合可以实现阻塞队列,生产者客户端lpush从左侧添加元素,多个消费者客户端brpop阻塞从右侧拿取元素,保证了负载均衡和高可用
文章列表
每个用户有自己的文章列表,需要分页展示文章列表。可使用list
list作为栈、队列、有限集合、消息队列使用
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
集合(set)
集合中不允许有重复元素,且元素是无序的,不能通过下标获取元素,至多可存储2^32 -1 个元素。
Redis除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
集合命令包含集合内部和集合之间2个维度的命令区分
集合内命令
添加元素:asdd key element [element1...]
返回结果为成功添加的元素个数
删除元素:srem key element [element1...]
返回结果为成功删除的元素个数
计算元素个数:scard key
时间复杂度为O(1),不会遍历所有元素
判断元素是否在集合中:sismember key element
如果element元素在集合中返回1,不在返回0
随机从集合返回指定个数元素:srandmember key [count]
count为返回的个数,不写默认为1
随机从集合中弹出指定个元素:spop key [count]
返回弹出的元素,弹出后元素不在集合中
count为返回的个数,不写默认为1
获取所有元素:smembers key
返回的结果是无序的
集合间命令
求多个集合的交集:sinter key [key1...]
求多个集合的并集:suinon key [key1...]
求多个集合的差集:sdiff key [key1...]
将交集、并集、差集的结果保存到集合:sinterstore|suinonstore|sdiffstore destination_key key [key1...]
(原命令+store)将集合间交集、并集、差集的结果保存在destination_key集合中
如果原来的destination_key集合的有元素,则会被删除
内部编码
集合内部编码有两种:intset(整数集合)、hashtable(哈希表)
intset:集合中的元素都是整数,且元素个数小于512个配置时,选择intset减少内存使用
hashset:intset无法满足条件时选用hashset
使用场景
用户标签(tag),得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要
用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致
sadd =Tagging(标签)
spop/srandmember=Random item(生成随机数,比如抽奖)
sadd+sinter=Social Graph (社交需求)
有序集合(zset)
保留了set不能有重复元素的特性,但可排序。其元素通过设置一个分数(score)作为排序依据,且score可以重复
有序集合内命令
添加成员:zadd key score member [socre1 member1...]
返回成功添加的成员member个数
计算成员个数:zcard key
时间复杂度为O(1)
计算某个成员的分数:zscore key member
返回成员的分数,没有则返回nil
计算成员的排名:zrank|zrevrank key member
zrank由低到高排名,zrevrank由高到低排名
删除成员:zrem key member [member1...]
返回删除成员的个数
增加成员的分数:zincrby key increment member
给成员member增加increment分数(可为负数)
返回增加后的member的score
返回指定排名范围内的成员:zrange|zrevrange key start end [withscores]
zrange由低到高返回(zrevrange由高到低)返回按照score排名在key和start之间(包含)的member(如果有withscores则还返回对应的socre分数)。排名从0开始
返回指定score分数范围的成员:zrangebyscore key min max[withscores] [limit offset count]、zrevrangebyscore key max min[withscores] [limit offset count]
- zrangebyscore的score分值由低到高返回(zrevrangebyscore的score分值由高到低)返回min和max之间的成员(如果有withscores则还返回对应的socre分数)。
- 可加入参数-inf、+inf代表无限小和无限大
返回指定分数范围成员个数:zcount key min max
删除指定排名内的升序元素:zremrangebyrank key start end
删除升序排名的start到end的元素
返回删除的元素个数,索引从0开始
删除指定分数范围内的成员:zremrangebyscore key min max
删除分数在min max内的成员(可包含min和max),返回成功删除的元素个数
有序集合间命令
交集:zinterstore destination_key numkeys key [key1...] [weights weight [weight1...]] [aggregate]
- destination_key:交集计算的结果要保存到的键
- numkkeys:需要作交集计算键的个数
- key [key1...]:需要作交集计算的键
- weights weight [weight1...]:每个键的权重,做交集运算时,每个键中的每个member会将自分数乘以这个权重,每个键的默认权重为1
- aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值时sum。
并集
内部编码
ziplist(压缩列表)
有序集合zset的元素个数小于配置128个、且每个元素的值小于配置64字介石,使用减少内存的消耗
skiplist(跳跃表)
当ziplist条件不满足时
使用场景
- 排行榜:
对视频或用户的排行情况,榜单的维度可能是多个方面的,例如视频:按照时间、播放数量、获得的赞数的情况综合。
键管理
按照单个键、遍历键、数据库管理三个维度介绍
单个键管理
除了type、del、object、exists、expire等之外还有其他常用
键重命名:rename key newkey(会覆盖newkey)
- 将key重新命名为newkey
- 如果newkey之前已经存在,则会覆盖其值
键重命名:renamenx key newkey(如果newkey存在则不执行重命名)
如果newkey存在,则返回0代表未重命名
随即返回一个键:randomkey
键过期:expire、ttl、expireat、pexpire、pexpireat、pttl、persist
除了expire、ttl基础命令之外还有expireat、pexpire、pexpireat、pttl、persist等命令
-
expire key seconds:key键在seconds秒后过期
-
expireat key timestamp:key键在秒级时间戳timestamp后过期
-
ttl:查询剩余时间(秒级)
返回大于0则未剩余过期时间
返回-1则未设置过期时间
返回-2则键不存在 -
pttl:查询剩余时间(毫秒级)
-
注意:
1.expire key的键不存在时,返回0
2.如果过期时间值设置负数,会被立刻删除,相当于del
3.persist 可以将键的过期时间清除
4.字符串类型的set会清除掉过期时间
5.Redis不支持设置二级数据结构(hash、list)内部元素的过期功能
6.setex相当于set+expire,原子性,且减少了网络连接通讯的时间
键迁移
有时候我们只想把部分数据由一个Redis迁移到另一个Redis(正式迁移到测试),Redis有move、dump+restore、migrate方法
move:Redis内部数据库迁移
move key db
用于Redis内部进行数据迁移,Redis内部有多个数据库,把指定的键从原数据库移动到目标数据库
dump+restore:Redis实例之间数据库迁移
dump key
restore key ttl value
-
过程
1.在源Redis上,dump将键值序列化,格式为RDB
2.在目标Redis上,resotre将序列化的值进行复原,ttl为过期时间,ttl=0代表没有过期时间 -
注意点
1.迁移过程非原子性,通过客户端分步完成
2.开启了2个客户端,dump的结果不是在源Redis和目标Redis之间传输
migrate:在Redis实例间进行数据库迁移(推荐)
migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key1 ...]]
migrate命令为将dump、resotre、del三个命令组合,简化了流程操作
-
优点
1.具有原子性
2.支持迁移多个键的功能,提高迁移效率 -
参数说明
1.host:目标Redis的IP地址
2.port:目标Redis的端口
3.key|"" :迁移单个键key,使用key。迁移多个键则用空格
4.destination-db:目标Redis的数据库索引,例如0号数据库则为0
5.timeout:迁移的超时时间(毫秒级)
6.copy:添加此选项则迁移后不删除源键,不添加则删除
7.replace:添加此选项则不管目标Redis是否存在该键都会挣钱迁移进行数据覆盖
8.[keys key [key1 ...]]:迁移多个键,例如[keys a b c] -
过程
1.原子性,不需要在多个Redis实例上开启客户端
2.数据传输直接在源Redis和目标Redis上完成
3.目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键
遍历键
Redis提供2个遍历所有的键命令,keys、scan
全量遍历键:keys pattern
keys命令支持pattern,可以使用通配符
- *:代表匹配任意字符
- ?:匹配一个字符
- []:匹配部分字符,例如[1,3]:代表匹配1,3。[1-10]代表匹配1-10
- \:转义字符,
总结:keys命令可能会造成redis阻塞,不建议在在生产环境使用keys命令,可以使用scan命令渐进式遍历所有键,可以有效防止阻塞
渐进式遍历:scan、hscan、sscan、zscan
scan cursor [match pattern] [count number]
scan采用渐进式遍历的方式来解决keys命令会产生的阻塞,每次scan命令的时间复杂度是O(1),但是要遍历所有的key要执行多次scan
- sursor:游标,第一次遍历从0开始,每次scan遍历完都会参会当前游标的值,直到游标为0,遍历结束
- match pattern:可选参数,匹配模式
- count number:可选参数,表明每次要遍历的键个数,默认值为10,可适当增大
数据库管理
面向数据库的操作dbsize、select、flushdb、flushall
切换数据库:select dbindex
redis用数字多为多个数据库的实现,默认配置是有16个数据库(0-15)。
数据库之间的数据没有任何关联,可以存在相同的键
当使用默认的redis-cli -h[ip] -p[port]连接Redis默认使用0号数据库,选择其他数据库时会有[index]表示如上图
不建议使用Redis的多数据库功能,如果要使用可以部署多个Redis实例来实现理由如下
- Redis为单线程,使用同一个CPU,会受到速度影响,部署多个实例可以使用多核CPU
- 多数据库的使用会让调试和运维不同业务数据变得困难
- 部分Redis客户端不支持该方式,且来回切换数据库使得操作容易错误
清除数据库:flushdb/flushall
flushdb:清除当前数据库数据
flushall:清除所有数据库数据
注意:
- flushdb/flushall误操作使用清除后,后果影响较大,可使用rename-command配置规避这个问题
- flushdb/flushall在数据库键值数量较多时可能会存在阻塞Redis的可能性
Comments | 0 条评论