预备

Redis的全局命令、数据结构和内部编码、单线程命令处理机制

全局命令

Redis有5种数据结构,他们是K-V中的V,有一些通用的命令

查看所有键:keys *

会遍历所有键,所以时间复杂度为O(n),当Redis保存了大量键时,线上环境禁止使用
image.png

键总数:dbsize

不会遍历所有键,直接获取Redis内置的键总数变量,所以复杂度为O(1)
image.png
image.png

检查键是否存在:exists key

如果存在则返回1,不存在返回0
image.png

删除键:del key [key ...]

可删除一个或多个键,返回的结果为成功删除的键的个数
image.png

键过期:expire key seconds ttl key

  • expire key seconds:设置过期时间
    对键添加过期时间,超过过期时间后会自动删除键

  • ttl key:查看剩余过期时间
    返回值大于0则显示剩余的秒数
    -1则没有设置过期时间
    -2表示键不存在
    image.png

键的数据结构类型:type key

如果键不存在,则返回none
image.png

数据结构和内部编码

type命令返回的是当前键的数据结构类型,分别是string、hash、list、set、zset,且每种数据结构都有自己底层的内部编码实现
image.png

  • 好处:
    1.方便改进内部编码,而对外的数据结构和命令没有影响,例如Redis 3.2增加的quicklist
    2.多种内部编码实现在不同场景下发挥各自的优势

查询内部编码:object encoding key

image.png
当键不存在时返回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的键都是字符串类型

数据结构为字符串的值

image.png
即上图中的string的值
最大不得超过512MB,字符串的值可以为:

  • 简单的字符串
  • 复杂的字符串:JSON、XML
  • 数字:整数、浮点数
  • 二进制:图片、音频、视频

常用命令

设置值:set key value [ex seconds] [px milliseconds] [nx|xx]

  • ex seconds:设置秒级过期时间,可连写setex
  • px milliseconds:设置毫秒级过期时间
  • nx:键必须不存在,才可以设置成功,用于添加,可连写setnx
  • xx:键必须存在,才可以设置成功,用于更新
    image.png

setnx的作用:分布式锁的实现方案

Redis是单线程处理,所以多个客户端同时执行setnx key value时,由于只有一个客户端可以设置成功,setnx可以作为分布式锁的实现方案之一,该官方网页介绍如下
https://redis.io/topics/distlock

获取值:get key

如果不存在,返回nil(空)

批量设置值:mset key value [key1 value1 key2 value2 ...]

image.png

批量获取值:mget key key1 key2 ...

如果key不存在则返回nil
image.png

批量操作意义:提高开发效率

如果没有批量操作则执行n次需要n次网络时间,而批量操作则只需要1次网络时间。Redis支持每秒数万的读写操作,但这指的是Redis服务端的处理能力,客户端还需要网络时间延迟。因为Redis的处理能力非常高,所以网络会称为性能瓶颈。每次批量操作发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞

计数:incr key

对值作自增操作+1,有三种情况

  • 不是整数,返回错误
  • 是整数,返回+1后的结果
  • key不存在,按照0,第一次执行为0+1=1
    image.png

不常用命令

追加值: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个字节的字符串
    image.png

典型使用场景

缓存功能

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}...}
image.png

命令

设置值:hset key field value

设置成功返回1,失败0,hsetnx 类似setnx,只是作用域变为field
image.png

获取值:hget key field

如果键或field不存在返回nil

删除field:hdel key field [field...]

删除一个或多个field,返回成功删除的个数
image.png

计算field个数:hlen key

返回key 对应的feild个数,如果不存在返回0
image.png

批量设置或获取field-value:hmset key filed value [filed1 value1 ...]、hmget key field [field...]

image.png

判断field是否存在:hexists key field

存在返回1,不存在返回0
image.png

获取所有field:hkeys key

返回hash键对应的所有field
image.png

获取所有value:hvals key

image.png

获取所有的field-value:hgetall key

如果hash元素个数比较多,会存在阻塞Redis的可能
image.png

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此时读写效率会下降)
image.png

使用场景

缓存数据,例如用户信息
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在操作上会更加便捷

  • 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特点:

  • 有序,即可以通过索引下标获取元素
  • 可重复:可包含两个相同的字符串
    image.png

添加命令

从右边插入元素:rpush key value [valee1 ...]

image.png

从左到右遍历元素:lrang key 0 -1

从左边插入元素 lpush key value [valee1 ...]

image.png

向某个元素前/后插入元素:insert key before|after pivot(需要比较的元素element) value

从列表中找到等于piovt的元素,在前/后插入一个新的元素value,并返回操作后的列表长度
image.png
注意上图始终与元素相同时,以下标小为准

查找命令

获取指定范围内的元素列表:lrange key start end

获取索引为 start 和end 的范围元素
image.png

获取列表指定索引下标的元素:lindex key index

image.png

获取列表长度:llen key

image.png

删除命令

从列表左侧弹出元素:lpop key

返回弹出的元素的值
image.png

从列表右侧弹出:rpop key

image.png

删除指定元素:lrem key count value

从列表中找到等于value的元素进行删除,根据count不同

  • count>0,从左到右,删除最多count个元素
  • count<0,从右到左,删除最多count绝对值个元素
  • count=0,删除所有

image.png

按照索引范围修建列表:ltrim key start end

保留下标为star到end的元素
image.png

修改

修改指定索引下标的元素:lset key index newValue

修改索引下标为index的值为newValue
image.png

阻塞操作:blpop key [key ...] timeout、brpop key [key ...] timeout

  • blpop和brpop是lpop和rpop的阻塞版本
  • key表示多个列表的键
  • timeout为阻塞时间
    1.如果列表为空,则等待timeout秒后返回,若timeout=0则一直阻塞等待下去
    2.如果列表不为空,客户端会立即返回
    image.png

内部编码

三种类型

ziplist(压缩列表)

列表中的元素个数小于512个且每个元素值小于64字节,Redis选用ziplist来作为列表的内部实现,减少内存的使用

linkedlist

ziplist无法满足时使用

quicklist

以ziplist为节点的linkedlist

使用场景

消息队列

lpush+brpoop组合可以实现阻塞队列,生产者客户端lpush从左侧添加元素,多个消费者客户端brpop阻塞从右侧拿取元素,保证了负载均衡和高可用
image.png

文章列表

每个用户有自己的文章列表,需要分页展示文章列表。可使用list

list作为栈、队列、有限集合、消息队列使用

  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

集合(set)

集合中不允许有重复元素,且元素是无序的,不能通过下标获取元素,至多可存储2^32 -1 个元素。
Redis除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
image.png

集合命令包含集合内部和集合之间2个维度的命令区分

集合内命令

添加元素:asdd key element [element1...]

返回结果为成功添加的元素个数

删除元素:srem key element [element1...]

返回结果为成功删除的元素个数

计算元素个数:scard key

时间复杂度为O(1),不会遍历所有元素
image.png

判断元素是否在集合中:sismember key element

如果element元素在集合中返回1,不在返回0

随机从集合返回指定个数元素:srandmember key [count]

count为返回的个数,不写默认为1
image.png

随机从集合中弹出指定个元素:spop key [count]

返回弹出的元素,弹出后元素不在集合中
count为返回的个数,不写默认为1

获取所有元素:smembers key

返回的结果是无序的
image.png

集合间命令

求多个集合的交集:sinter key [key1...]

求多个集合的并集:suinon key [key1...]

求多个集合的差集:sdiff key [key1...]

image.png

将交集、并集、差集的结果保存到集合:sinterstore|suinonstore|sdiffstore destination_key key [key1...]

(原命令+store)将集合间交集、并集、差集的结果保存在destination_key集合中
如果原来的destination_key集合的有元素,则会被删除
image.png

内部编码

集合内部编码有两种:intset(整数集合)、hashtable(哈希表)

intset:集合中的元素都是整数,且元素个数小于512个配置时,选择intset减少内存使用

hashset:intset无法满足条件时选用hashset

image.png

使用场景

用户标签(tag),得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要
用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致

sadd =Tagging(标签)
spop/srandmember=Random item(生成随机数,比如抽奖)
sadd+sinter=Social Graph (社交需求)

有序集合(zset)

image.png
保留了set不能有重复元素的特性,但可排序。其元素通过设置一个分数(score)作为排序依据,且score可以重复

有序集合内命令

添加成员:zadd key score member [socre1 member1...]

返回成功添加的成员member个数

计算成员个数:zcard key

时间复杂度为O(1)
image.png

计算某个成员的分数:zscore key member

返回成员的分数,没有则返回nil
image.png

计算成员的排名:zrank|zrevrank key member

zrank由低到高排名,zrevrank由高到低排名
image.png

删除成员:zrem key member [member1...]

返回删除成员的个数
image.png

增加成员的分数:zincrby key increment member

给成员member增加increment分数(可为负数)
返回增加后的member的score
image.png

返回指定排名范围内的成员:zrange|zrevrange key start end [withscores]

zrange由低到高返回(zrevrange由高到低)返回按照score排名在key和start之间(包含)的member(如果有withscores则还返回对应的socre分数)。排名从0开始
image.png

返回指定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代表无限小和无限大
    image.png

返回指定分数范围成员个数:zcount key min max

image.png

删除指定排名内的升序元素:zremrangebyrank key start end

删除升序排名的start到end的元素
返回删除的元素个数,索引从0开始
image.png

删除指定分数范围内的成员:zremrangebyscore key min max

删除分数在min max内的成员(可包含min和max),返回成功删除的元素个数
image.png

有序集合间命令

交集: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
    image.png
  • 如果newkey之前已经存在,则会覆盖其值
    image.png

键重命名:renamenx key newkey(如果newkey存在则不执行重命名)

如果newkey存在,则返回0代表未重命名
image.png

随即返回一个键:randomkey

image.png

键过期: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:查询剩余时间(毫秒级)
    image.png

  • 注意:
    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
  • \:转义字符,
    image.png

总结: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,可适当增大
    image.png

数据库管理

面向数据库的操作dbsize、select、flushdb、flushall

切换数据库:select dbindex

redis用数字多为多个数据库的实现,默认配置是有16个数据库(0-15)。
数据库之间的数据没有任何关联,可以存在相同的键
image.png
当使用默认的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的可能性

这个家伙很懒,啥也没有留下😋