Redis数据类型-字符串对象
- 字符串对象
- 字符串对象 保存 各类型值 的 编码方式(重要!!!)
- embstr编码(重要!!!)
- 编码的转换
- 字符串对象的命令(包括不同编码情况下的实现方法)
- 常用命令(Redis开发与运维中内容)
- 1.设置值-set、setex、setnx
- 2.获取值-get
- 3.批量设置值-mset、msetnx
- 4.批量获取值-mget
- 5.计数-incr、decr、incrby、decrby
- 不常用命令
- 1.追加值-append
- 2.字符串长度-strlen
- 3.设置并返回原值-getset
- 4.设置指定位置的字符-setrange
- 4.获取部分字符串-getrange
- 时间复杂度
- 使用场景(重要!!!)
字符串对象
字符串类型 的 值 实际可以是 字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB
字符串对象的编码 可以是 int
、raw
或者embstr
- int:8个字节的长整型
- embstr:小于等于39个字节的字符串
- raw:大于39个字节的字符串
回顾一下:
对象(RedisObject)的ptr
指针 指向 对象的 底层实现数据结构,而 这些数据结构 由 对象(RedisObject)的encoding
属性决定
encoding
属性值常量如下:
使用OBJECT ENCODING
命令 可以查看 一个数据库键 的 值对象 的 编码
如果 字符串对象 保存的 是 整数值,并且 这个整数值 可以用 long类型 来表示,那么 字符串对象 会将 整数值 保存在 字符串对象结构的ptr
属性里面(将void*转换成long),并将 字符串对象 的 编码 设置为 int,如下所示:
redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"
如果 字符串对象 保存的是 字符串值,并且 这个字符串值的长度 大于 32字节,那么 字符串对象 将使用 一个简单动态字符串(SDS) 来保存 这个字符串值,并将 对象 的 编码 设置为 raw,如下所示:
redis> SET story "Long, long ago there lived a king ..."
OK
redis> STRLEN story
(integer) 37
redis> OBJECT ENCODING story
"raw"
如果 字符串对象 保存的是 字符串值,并且 这个字符串值的长度 小于等于 32字节,那么 字符串对象 将使用 embstr编码的方式 来保存 这个字符串值,如下所示:
redis> SET msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"
可以用long double类型表示的浮点数 在 Redis中 也是 作为字符串值来保存的
如果 要保存一个 浮点数 到 字符串对象里面,那么 程序 会先将 这个浮点数 转换成 字符串值,然后 再保存 转换所得的字符串值
redis> SET pi 3.14
OK
redis> OBJECT ENCODING pi
"embstr"
在有需要的时候,程序 会将 保存在字符串对象里面的字符串值 转换回 浮点数值,执行某些操作,然后 再将 执行操作所得的浮点数值 转换回 字符串值,并继续保存在字符串对象里面
redis> INCRBYFLOAT pi 2.0
"5.14"
redis> OBJECT ENCODING pi
"embstr"
字符串对象 保存 各类型值 的 编码方式(重要!!!)
embstr编码(重要!!!)
embstr编码 是 专门用于保存 短字符串 的 一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象
但 raw编码 会调用两次 内存分配函数 来分别创建 redisObject结构和sdshdr结构
而embstr编码 则通过 调用一次内存分配函数 来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构
embstr编码的字符串对象在执行命令时,产生的效果和raw编码的字符串对象执行命令时产生的效果是相同的
但使用embstr编码的字符串对象来保存短字符串值有以下好处:
- embstr编码 将 创建字符串对象 所需的内存 分配次数 从raw编码的两次降低为一次
- 释放 embstr编码的字符串对象 只需要 调用一次内存释放函数,而 释放 raw编码的字符串对象 需要 调用两次内存释放函数
- 因为 embstr编码的字符串对象 的 所有数据 都保存在 一块连续的内存里面,所以 这种编码 的 字符串对象 比起 raw编码的字符串对象 能够更好地利用缓存带来的优势
编码的转换
int编码的字符串对象 和 embstr编码的字符串对象 在 条件满足的情况下,会被 转换为 raw编码的字符串对象
对于int编码的字符串对象来说,如果向对象执行了一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,那么字符串对象的编码将从int变为raw
redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"
redis> APPEND number " is a good number!"
(integer) 23
redis> GET number
"10086 is a good number!"
redis> OBJECT ENCODING number
"raw"
因为Redis 没有为 embstr编码的字符串对象 编写 任何相应的修改程序(只有 int编码的字符串对象 和 raw编码的字符串对象 有这些程序),所以 embstr编码的字符串对象 实际上 是只读的
当 对embstr编码的字符串对象 执行 任何修改命令时,程序 会先将 对象的编码从embstr转换成raw,然后再执行修改命令
因为这个原因,embstr编码的字符串对象 在执行 修改命令之后,总会变成一个raw编码的字符串对象
redis> SET msg "hello world"
OK
redis> OBJECT ENCODING msg
"embstr"
redis> APPEND msg " again!"
(integer) 18
redis> OBJECT ENCODING msg
"raw"
字符串对象的命令(包括不同编码情况下的实现方法)
常用命令(Redis开发与运维中内容)
1.设置值-set、setex、setnx
set key value [ex seconds] [px milliseconds] [nx|xx]
返回结果为OK代表设置成功:
127.0.0.1:6379> set hello world
OK
set
命令有几个选项:
- ex seconds:为键设置秒级过期时间
- px milliseconds:为键设置毫秒级过期时间
- nx:键必须不存在,才可以设置成功,用于添加
- xx:与nx相反,键必须存在,才可以设置成功,用于更新
除了set
选项,Redis还提供了setex
和setnx
两个命令:
setex key seconds value
setnx key value
它们的作用和ex和nx选项是一样的
当前键hello不存在:
127.0.0.1:6379> exists hello
(integer) 0
127.0.0.1:6379> set hello world
OK
因为键hello已存在,所以setnx失败,返回结果为0:
127.0.0.1:6379> setnx hello redis
(integer) 0
因为键hello已存在,所以setex成功,返回结果为OK:
127.0.0.1:6379> setex hello jedis
OK
setnx
和setxx
在实际使用中有什么应用场景吗?(面试题)
以setnx
命令为例子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value
,根据setnx的特性只有一个客户端能设置成功,setnx
可以作为分布式锁的一种实现方案
Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock
2.获取值-get
get key
下面操作获取键hello的值:
127.0.0.1:6379> get hello
"world"
如果要获取的键不存在,则返回nil(空):
127.0.0.1:6379> get not_exist_key
(nil)
3.批量设置值-mset、msetnx
mset key value [key value ...]
下面操作通过mset
命令一次性设置4个键值对:
127.0.0.1:6379> mset a 1 b 2 c 3 d 4
OK
msetnx与mset的主要区别在于,msetnx 只会在 所有给定键 都不存在的情况下 对键 进行设置
而不会像mset那样直接覆盖键已有的值
如果在给定键当中,即使有一个键已经有值了,那么msetnx命令也会放弃对所有给定键的设置操作
msetnx命令在成功执行设置操作时返回1,在放弃执行设置操作时则返回0
msetnx的时间复杂度是O(k)
4.批量获取值-mget
mget key [key ...]
下面操作批量获取了键a、b、c、d的值:
127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"
如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回:
127.0.0.1:6379> mget a b c f
1) "1"
2) "2"
3) "3"
4) (nil)
批量操作命令可以有效提高开发效率,假如没有mget
这样的命令,要执行n次get命令具体耗时如下:
n次get时间 = n次网络时间 + n次命令时间
使用mget
命令后,要执行n次get命令操作具体耗时如下:
n次get时间 = 1次网络时间 + n次命令时间
5.计数-incr、decr、incrby、decrby
很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行
incr key
incr命令用于对值做自增操作,返回结果分为三种情况:
- 值不是整数,返回错误
- 值是整数,返回自增后的结果
- 键不存在,按照值为0自增,返回结果为1
例如对一个不存在的键执行incr操作后,返回结果是1:
127.0.0.1:6379> exists key
(integer) 0
127.0.0.1:6379> incr key
(integer) 1
再次对键执行incr
命令,返回结果是2:
127.0.0.1:6379> incr key
(integer) 2
如果值不是整数,那么会返回错误:
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> incr hello
(error) ERR value is not an integer or out of range
除了incr
命令,Redis提供了decr
(自减)、incrby
(自增指定数字)、decrby
(自减指定数字)、incrbyfloat
(自增浮点数):
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
incrby和decrby的增量和减量也必须能够被Redis解释为整数
incrby、decrby命令遇到不存在的键时,会先将键的值初始化为0,然后再执行相应的加法操作或减法操作
incrbyfloat命令遇到不存在的键时,会先将键的值初始化为0,然后再执行相应的加法操作
只能通过 给incrbyfloat命令 传入 负数增量 来执行 浮点数减法操作
在使用incrbyfloat命令处理浮点数的时候,命令最多只会保留计算结果小数点后的17位数字,超过这个范围的小数将被截断
不常用命令
1.追加值-append
如果给定的键并不存在,那么append 命令会先将键的值初始化为空字符串"",然后再执行追加操作
最终效果与使用set命令为键设置值的情况类似
append key value
append
可以向字符串尾部追加值,例如:
127.0.0.1:6379> get key
"redis"
127.0.0.1:6379> append key world
(integer) 10
127.0.0.1:6379> get key
"redisworld"
2.字符串长度-strlen
strlen key
例如,当前值为redisworld,所以返回值为10:
127.0.0.1:6379> get key
"redisworld"
127.0.0.1:6379> strlen key
(integer) 10
下面操作返回结果为6,因为每个中文占用3个字节:
127.0.0.1:6379> set hello "世界"
OK
127.0.0.1:6379> strlen hello
(integer) 6
3.设置并返回原值-getset
getset key value
getset
和set
一样会设置值,但是不同的是,它同时会返回 键原来的值,例如:
127.0.0.1:6379> getset hello world
(nil)
127.0.0.1:6379> getset hello redis
"world"
4.设置指定位置的字符-setrange
setrange key offeset value
下面操作将值由pest变为了best:
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"
当给定的新内容 比 被替换的内容更长时,setrange命令就会自动扩展被修改的字符串值,从而确保新内容可以顺利写入
当给定的 index索引 超出 字符串值的长度时,字符串值末尾 直到 索引index-1之间 的 部分 将使用 空字节进行填充
换句话说,这些字节的所有二进制位都会被设置为0
4.获取部分字符串-getrange
getrange key start end
start
和end
分别是开始和结束的偏移量,偏移量从0开始计算,例如下面操作获取了值best的前两个字符
127.0.0.1:6379> getrange redis 0 1
"be"
时间复杂度
使用场景(重要!!!)
- 缓存功能
比较典型的缓存使用场景 是 Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取
由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用 - 计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。例如视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1 - 共享Session
一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。了解决这个问题,可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取 - 限速
很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次