Redis 从放弃到入门
Redis 从放弃到入门
初识Redis
Redis是什么
Redis是基于键值对(key-value)的NoSql非关系型数据库
Redis特性
-
速度快 (读写性能 10万/秒)
- 数据存放于内存中 (内存在各硬件执行速度上最快)
- 基于c语言开发
- 单线程架构
- 开源代码
各硬件执行数速度:
-
基于键值对的数据结构服务器
-
丰富的功能
- 提供键过期功能,实现缓存
- 提供发布订阅功能,实现消息系统
- 支持lua脚本,利用lua实现新的redis功能
- 提供简单的事务,一定程度保证事务特性
- 提供流水线(pipline)功能,客户端可一次性将批量命令传到redis,减少网路开销
-
简单稳定
- redis源码少
- 单线程模式
- 不依赖于操作系统类库(如 memcache 依赖libevent库类库)
-
客户端语言多
redis提供了简单的TCP通讯协议,方便多语言直接接入 。如 java/php/nodeJs/C++等 -
持久化 (将内存中数据保存至硬盘中)
redis提供两张持久化数据方式:- RDB
- AOF
-
主从复制
-
高可用和分布式
Redis能做什么
1、缓存
2、排行榜
3、计数器
4、社交网路
5、消息队列系统
Redis 的数据类型
- 字符串
- 列表
- 哈希
- 有序集合
- 无序集合
字符串
1、命令
1.1设置字符串
set key value [ex seconds] [px milliseconds] [nx|xx]
set命令有几个选项:
- ex seconds:为键设置秒级过期时间。 ·
- px milliseconds:为键设置毫秒级过期时间。 ·
- nx:键必须不存在,才可以设置成功,用于添加。 ·
- xx:与nx相反,键必须存在,才可以设置成功,用于更新。
set的扩展命令
setex
和setxx
作用和ex和nx选项是一样的。
1.2获取值 get
get key
不存在则返回nil (空)
1.3 批量设置值
mset key value [key value ...]
1.4 批量获取值 不存在的值返回nil (空)
mget key [key ...]
1.5 计数
incr key
返回3种结果:
- 值不是整数,返回错误
- 值是整数,返回自增后的结果
- 键不存在,默认值为0 自增,返回结果1
除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数)
很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的 CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任 何命令到了Redis服务端都要顺序执行。
1.5 不常用命令
(1)追加值 append append key value
(2)字符串长度 strlen key
返回value值的长度, 每个中文占用3个字节
(3)设置并返回原值getset key vaule
,getset
和set
一样会设置值,但是不同的是,它同时会返回键原来的值
127.0.0.1:6379> getset hello world
(nil)
127.0.0.1:6379> getset hello redis
"world"
(4)设定指定位置的字符setrange key offset value
127.0.0.1:6379> set redis pest
OK
127.0.0.1:6379> setrange redis 0 b
(integer) 4
127.0.0.1:6379> get redis
"best"
(5)获取部分字符串getrange key start end
,start
和end
分别是开始和结束的偏移量,偏移量从0开始计算
127.0.0.1:6379> getrange redis 0 1
"be"
2、内部编码
字符串类型的内部编码有3种:
int
: 8个字节长整型embstr
: 不大于39字节的字符串raw
: 大于39字节的字符串
查看key类型命令
object encoding key
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"
哈希
1、命令
1.1设置值hset key field value
1.2、获取值hget key field
1.3、删除值hdel key field [field ...]
返回成功删除的个数
1.4、计算field个数hlen key
1.5、批量设置或获取field-value 针对同一个key,多个field
hmset key field [field ...]
hmset key field value [field value ...]
1.6、判断field是否存在hexists key filed
存在返回1 ,不存在返回0
1.7、获取所有的fieldhkeys key
1.8、获取key下所有的valuehvals key
1.9、获取所有的field-valuehgetall key
1.10、field值自增hincrby 、hincrbyfloat
和incrby
和incrbyfloat
命令一样,但是它们作用域是filed
1.11、计算value的字符串长度(需要Redis3.2以上)hstrlen key field
哈希使用部分示例:
redis 127.0.0.1:6379> hset usr:1 name timor
(integer) 1
redis 127.0.0.1:6379> hget usr:1 // 只获取获取key会报错
(error) ERR wrong number of arguments for 'hget' command
redis 127.0.0.1:6379> hget usr:1 name // 通过获取field获取值
"timor"
redis 127.0.0.1:6379> hget usr:1 age // 不存在则返回nil
(nil)
redis 127.0.0.1:6379> hdel usr:1 name // 返回删除个数
(integer) 1
redis 127.0.0.1:6379> hdel usr:1 age
(integer) 0
redis 127.0.0.1:6379> hmset user:1 name timor age 35 city xiamen
OK
redis 127.0.0.1:6379> hmget user:1 name city
1) "timor"
2) "xiamen"
redis 127.0.0.1:6379> hkeys user:1
1) "name"
2) "city"
3) "age"
redis 127.0.0.1:6379> hgetall user:1 // 返回格式为filed 、value ...
1) "name"
2) "mike"
3) "city"
4) "xaimen"
5) "age"
6) "35"
2、内部编码
哈希类型的内部编码有2种:
- ziplist(压缩列表):当哈希类型元素个数小于
hash-max-ziplist-entries
配置(默认512个)、同时所有值都小于hash-max-ziplist-value
配置(默认64 字节) - hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使 用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)
ziplist使用更加紧凑的结构实现多个元素的连续存储,在节省内存方面比hashtable更加优秀
缓存用户信息3种方式比较:
1、原生字符串类型:每个属性一个键。
- 优点:简单直观,每个属性都支持更新操作。
- 缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差, 此种方案一般不会在生产环境使用。
2、序列化字符串类型:将用户信息序列化后用一个键保存
- 优点:简化编程,如果合理的使用序列化可以提高内存的使用效率
- 缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis
3、哈希类型:每个用户属性使用一对field-value,但只用一个键保存
- 优点:简单直观,如果使用合理可以减少内存空间的使用。
- 缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会 消耗更多内存
// 方式1:
set user:1:name tom
set user:1:age 23
set user:1:city beijing
// 方式2:
set user:1 serialize(userInfo)
// 方式3:
hmset user:1 name timor age 35 city xiamen
列表
一个列表最多可以存储2^32-1个元素
1、操作示意图
(1)两端插入和弹出操作
(2)子列表获取和删除
2、列表的特点
- 列表中的元素是有序的 (即 可通过索引下标获取某个元素或者某个范围内的元素列表)
- 列表中的元素允许重复
3、命令
3.1 添加元素
* 从右边插入rpush key value [value ...]
* 从左边插入元素lpush key value [value ...]
* 向某个元素前或者后插入元素 linsert key before|after pivot value
linsert
命令会从列表中找到等于pivot
的元素,在其前(before
)或者后 (after
)插入一个新的元素value
3.2 查找
- 获取指定范围内的元素列表
lrange key start end
①索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
②lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同 - 获取列表指定索引下标的元素
lindex key index
- 获取列表长度
llen key
3.3 删除
- 从列表左侧弹出元素
lpop key
- 从列表右侧弹出
rpop key
- 删除指定元素
lrem key count value
count>0,从左到右,删除最多count个元素。 ·
count<0,从右到左,删除最多count绝对值个元素。
count=0,删除所有 - 按照索引范围修剪列表
ltrim key start end
3.4 修改指定索引下标的元素lset key index newValue
3.5 阻塞操作blpop key [key ...] timeout
,brpop key [key ...] timeout
1、如果多个键,那么brpop会从左至右遍历键,一旦有一个键 113
能弹出元素,客户端立即返回
2、如果多个客户端对同一个键执行brpop,那么最先执行brpop命 令的客户端可以获取到弹出的值
`blpop`和`brpop`是`lpop`和`rpop`的阻塞版本,它们除了弹出方向不同,使用方法基本相同
- `key[key...]`:多个列表的键。
- `timeout`:阻塞时间(单位:秒)
1) 列表为空:若 timeout=3,则客户端要等到3秒后返回,若 timeout=0,则客户端一直阻塞等待
2)列表不为空:客户端会立即返回
// 在b元素前插入新元素java,返回结果为4,代表当前命令的长度
127.0.0.1:6379> linsert listkey before b java
(integer) 4
// 元素列表值
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "java"
3) "b"
4) "a"
// 获取指定范围的元素
127.0.0.1:6379> lrange listkey 1 3
1) "java"
2) "b"
3) "a"
// 获取当前列表最后一个元素 a
127.0.0.1:6379> lindex listkey -1
"a"
// 下面操作会只保留列表listkey第2个到第4个元素
127.0.0.1:6379> ltrim listkey 1 3
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"
// 列表为空:若 timeout=3,则客户端要等到3秒后返回,若 timeout=0,则客户端一直阻塞等待
127.0.0.1:6379> brpop list:test 3
(nil)
(3.10s)
127.0.0.1:6379> brpop list:test 0
...阻塞...
// 如果此期间添加了数据element1,客户端立即返回
127.0.0.1:6379> brpop list:test 3
1) "list:test"
2) "element1"
(2.06s)
4、 内存编码
列表类型的内部编码有两种:
ziplist
(压缩列表):当列表的元素个数小于list-max-ziplist-entries
配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value
配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用linkedlist
(链表):当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现
Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节 点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现
5、使用场景
- 消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产 者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令 阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用 性 - 文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以 考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素
实际上列表的使用场景很多,在选择时可以参考以下口诀: - lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
集合
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过 索引下标获取元素。一个集合最多可以存储2^32-1个元素
1、命令
1.1 添加元素sadd key element [element ...]
返回成功添加的个数
1.2 删除元素srem key element [element ...]
返回成功删除的个数
1.3 计算元素个数scard key
scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用 Redis内部的变量
1.4 判断是否在集合中sismember key element
在集合内返回1,反之返回0
1.5 随机从集合返回指定个数元素srandmember key [count]
[count]是可选参数,不写则默认为1
1.6 从集合随机弹出元素spop key
Redis从3.2版本开始,spop也支持[count]参数
1.7 获取所有元素smembers key
返回结果是无序的
srandmember
和spop
都是随机从集合选出元素,两者不同的是spop
命令 执行后,元素会从集合中删除,而srandmember
不会
2、集合间操作
2.1 求多个集合的交集 sinter key [key ...]
2.2 求多个集合的并集 suinon key [key ...]
2.3 求多个集合的差集 sdiff key [key ...]
2.4 将交集、并集、差集的结果保存sinterstore destination key [key ...]
,suionstore destination key [key ...]
,sdiffstore destination key [key ...]
127.0.0.1:6379> exists myset // 检验key是否存在
(integer) 0
127.0.0.1:6379> sadd myset a b c // 添加元素
(integer) 3
127.0.0.1:6379> sadd myset a b // 添加重复元素
(integer) 0
127.0.0.1:6379> srem myset a b // 删除元素
(integer) 2
127.0.0.1:6379> srem myset hello // 删除不存在元素
(integer) 0
127.0.0.1:6379> scard myset // 计算元素个数
(integer) 1
127.0.0.1:6379> sismember myset c // 验证c是否在集合中
(integer) 1
127.0.0.1:6379> srandmember myset 2 //随机返回指定个数元素
1) "a"
2) "c"
// 集合间操作
127.0.0.1:6379> sadd user:1:follow it music his sports
(integer) 4
127.0.0.1:6379> sadd user:2:follow it news ent sports
(integer) 4
127.0.0.1:6379> sinter user:1:follow user:2:follow // 求两集合间交集
1) "sports"
2) "it"
127.0.0.1:6379> sunion user:1:follow user:2:follow // 求两集合并集
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
127.0.0.1:6379> sdiff user:1:follow user:2:follow // 求两集合差集
1) "music"
2) "his"
3、内部编码
集合类型的内部编码有2种:
intset
(整数集合):当集合中的元素都是整数且元素个数小于set-max- intset-entries
配置(默认512个)时,Redis会选用intset来作为集合的内部实 现,从而减少内存的使用。hashtable
(哈希表):当集合类型无法满足intset的条件时,Redis会使 用hashtable作为集合的内部实现。
4、使用场景
- 给用户添加标签
- 给标签添加用户
- 删除用户下的标签
- 删除标签下的用户
- 计算用户共同感兴趣的标签
集合类型的应用场景通常为以下几种:
sadd=Tagging(标签)
pop/srandmember=Random item(生成随机数,比如抽奖)
sadd+sinter=Social Graph(社交需求)
有序集合
1、命令
1.1 添加成员zadd key score member [score member ...]
返回结果代表成功添加成员的个数
Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
- nx:member必须不存在,才可以设置成功,用于添加。
- xx:member必须存在,才可以设置成功,用于更新。
- ch:返回此次操作后,有序集合元素和分数发生变化的个数
- incr:对score做增加,相当于后面介绍的zincrby。
1.2 计算成员个数zcard key
1.3 计算某个成员的分数zscore key member
成员不存在则返回nil
1.4 计算成员的排名zrank key member // 从低到高
,zrevrank key member // 从高到低
1.5 删除成员zrem key member [member ...]
返回成功删除的个数
1.6 增加成员的分数zincrby key increment member
1.7 返回指定排名范围的成员,跟上withscores选项同时会返回成员的分数
zrange key start end [withscores] //从低到高
zrevrange key start end [withscores] //从高到低
1.8 返回指定分数范围的成员,[limit offset count]选项可以限制输出的起始位置和个数
zrangebyscore key min max [withscores] [limit offset count] // 分数从低到高返回
zrevrangebyscore key max min [withscores] [limit offset count] // 分数从高到低返回
1.9 返回指定分数范围成员个数zcount key min max
1.10 删除指定排名内的升序元素zremrangebyrank key start end
1.11 删除指定分数范围的成员 zremrangebyscore key min max
2、集合间操作
2.1 交集zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
参数说明:
destination
:交集计算结果保存到这个键numkeys
:需要做交集计算键的个数key[key...]
:需要做交集计算的键weights weight[weight...]
:每个键的权重,在做交集计算时,每个键中 的每个member会将自己分数乘以这个权重,每个键的权重默认是1aggregate sum|min|max
:计算成员交集后,分值可以按照sum(和)、 min(最小值)、max(最大值)做汇总,默认值是sum
2.2 并集zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
参数个交集说明一致
3、内部编码
有序集合类型的内部编码有两种
ziplist
(压缩列表):当有序集合的元素个数小于zset-max-ziplist- entries
配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value
配 置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用skiplist
(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作 为内部实现,因为此时ziplist的读写效率会下降
4、使用场景
4.1 添加/取消 用户赞数
zadd user:ranking:2021_07_15 mike 3
// 用户mike获3个赞
zincrby user:ranking:2021_07_15 mike 1
// 获赞之后在获赞
zrem user:ranking:2021_07_15 mike
// 取消获赞,即删除成员
4.2 展示获取赞数最多的十个用户
zrevrangebyrank user:ranking:2021_07_15 0 9
4.3 展示用户信息以及用户分数
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike
zrank user:ranking:2016_03_15 mike
127.0.0.1:6379 > zadd user:ranking 251 tom // 添加
(integer) 1
127.0.0.1:6379 > zcard user:ranking // 计算个数
(integer) 1
127.0.0.1:6379 > zscore user:ranking tom
"251"
127.0.0.1:6379 > zincrby user:ranking 9 tom // 给tom成员增加9分
"260"
127.0.0.1:6379 > zrange user:ranking 0 2 withscores // 获取指定排名范围的成员
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379 > zrangebyscore user:ranking 200 tinf withscores //返回指定分数范围的成员
1) "frank"
2) "200"
3) "tim"
4) "220"
键管理
1、键重命名rename key newkey
在rename之前,新键名已经存在,那么它的值也将被覆盖 为了防止被强行rename,Redis提供了renamenx
命令,确保只有newKey 不存在时候才被覆盖
注意点:
- 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较 大,会存在阻塞Redis的可能性,这点不要忽视。 ·
- 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之 前版本返回结果略有不同Redis3.2中会返回OK,Redis3.2之前的版本会提示错误
2、随机返回一个键randomkey
3、键过期ttl key
无论使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat
返回结果:
-2
,说明键key已经被删除
-1
:键没有设置过期时间
>= 0
: 键剩余的过期时间(ttl是秒,pttl是毫秒) expire key seconds
:键在seconds秒后过期。expireat key timestamp
:键在秒级时间戳timestamp后过期- expireat命令可以设置键的秒级过期时间戳
expireat hello 1469980800
pexpire key milliseconds
:键在milliseconds毫秒后过期pexpireat key milliseconds-timestamp
键在毫秒级时间戳timestamp后过期
注意事项:
- 如果
expire key
的键不存在,返回结果为0 - 如果过期时间为负值,键会立即被删除
persist
命令可以将键的过期时间清除- 对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易 在开发中被忽视 示例如下:
- Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功 能,例如不能对列表类型的一个元素做过期时间设置。
setex
命令作为set+expire的组合,不但是原子执行,同时减少了一次 网络通讯的时间
127.0.0.1:6379 > expire hello 50
(integer) 1
127.0.0.1:6379 > ttl hello
(integer) 46
127.0.0.1:6379 > set hello world // 执行set命令
OK
127.0.0.1:6379 > ttl hello (integer) // 过期时间变为永久
-1
4、迁移键
4.1 move key db
move命令在Redis内部数据库之间迁移数据
4.2 dump+restore :
dump key
restore key ttl value
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整 个迁移的过程分为两步:
1)在源Redis上,dump命令会将键值序列化,格式采用的是RDB
格式。
2)在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参 数代表过期时间,如果ttl=0代表没有过期时间
注意点
7. 整个迁移过程并非原子性 的,而是通过客户端分步完成的。
8. 迁移过程是开启了两个客户端连 接,所以dump的结果不是在源Redis和目标Redis之间进行传输,
5、遍历键
5.1 全量遍历键 keys pattern
pattern使用的是glob风格的通配符:
*
代表匹配任意字符。 ·代表匹配一个字符。[]
代表匹配部分字符,例如[1,3]代表匹配1,3,[1-10]代表匹配1到10 的任意数字。\x
用来做转义,例如要匹配星号、问号需要进行转义。
5.2 渐进式遍历 scan cursor [match pattern] [count number]
cursor
是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每 次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。match pattern
是可选参数,它的作用的是做模式的匹配,这点和keys的 模式匹配很像。count number
是可选参数,它的作用是表明每次要遍历的键个数,默认 值是10,此参数可以适当增大。
scan采用渐进式遍历 的方式来解决keys命令可能带来的阻塞问题 【redis >=2.8版本】
6、数据库管理
6.1 切换数据库 select dbindex
Redis默认配置中是有16个数据库,编号[0-15]
6.2 flushdb/flushall
命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库。
注意点:
(1)flushdb/flushall命令会将所有数据清除,一旦误操作后果不堪设想,
(2)如果当前数据库键值数量比较多,flushdb/flushall存在阻塞Redis的可能性
功能分析
慢查询
1、慢查询日志定义:系统在命令执行前后计算每条命 令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时 间,耗时,命令的详细信息)记录下来
客户端命令生命周期:
1 发送命令 -> 2 命令排队 -> 3 命令执行 -> 4 返回结果
慢查询只统计步骤【3 执行命令】的时间,所以没有慢查询并不代表客 户端没有超时问题
2、慢查询参数配置
- 预设阀值
slowlog-log-slower-than
单位是微秒(1秒=1000毫秒=1000000微秒),默认值是10000。Redis使用了一个列表来存储慢查询日 志,slowlog-max-len就是列表的最大长度 - 慢查询最多存储条数
slowlog-max-len