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

字符串对象的编码 可以是 intraw或者embstr

  • int:8个字节的长整型
  • embstr:小于等于39个字节的字符串
  • raw:大于39个字节的字符串

回顾一下:

对象(RedisObject)的ptr指针 指向 对象的 底层实现数据结构,而 这些数据结构 由 对象(RedisObject)的encoding属性决定

encoding属性值常量如下:

redis opsForset 设置大小 redisobject 大小_字符串

使用OBJECT ENCODING命令 可以查看 一个数据库键 的 值对象 的 编码

redis opsForset 设置大小 redisobject 大小_字符串_02

如果 字符串对象 保存的 是 整数值,并且 这个整数值 可以用 long类型 来表示,那么 字符串对象 会将 整数值 保存在 字符串对象结构的ptr属性里面(将void*转换成long),并将 字符串对象 的 编码 设置为 int,如下所示:

redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"

redis opsForset 设置大小 redisobject 大小_数据类型_03

如果 字符串对象 保存的是 字符串值,并且 这个字符串值的长度 大于 32字节,那么 字符串对象 将使用 一个简单动态字符串(SDS) 来保存 这个字符串值,并将 对象 的 编码 设置为 raw,如下所示:

redis> SET story "Long, long ago there lived a king ..."
OK
redis> STRLEN story
(integer) 37
redis> OBJECT ENCODING story
"raw"

redis opsForset 设置大小 redisobject 大小_字符串_04

如果 字符串对象 保存的是 字符串值,并且 这个字符串值的长度 小于等于 32字节,那么 字符串对象 将使用 embstr编码的方式 来保存 这个字符串值,如下所示:

redis> SET msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"

redis opsForset 设置大小 redisobject 大小_Redis_05


可以用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"

字符串对象 保存 各类型值 的 编码方式(重要!!!)

redis opsForset 设置大小 redisobject 大小_embstr编码_06

embstr编码(重要!!!)

embstr编码 是 专门用于保存 短字符串 的 一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象

但 raw编码 会调用两次 内存分配函数 来分别创建 redisObject结构和sdshdr结构

而embstr编码 则通过 调用一次内存分配函数 来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构

redis opsForset 设置大小 redisobject 大小_数据类型_07

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 opsForset 设置大小 redisobject 大小_redis_08


redis opsForset 设置大小 redisobject 大小_字符串_09

常用命令(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还提供了setexsetnx两个命令:

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

setnxsetxx在实际使用中有什么应用场景吗?(面试题)

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

getsetset一样会设置值,但是不同的是,它同时会返回 键原来的值,例如:

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命令就会自动扩展被修改的字符串值,从而确保新内容可以顺利写入

redis opsForset 设置大小 redisobject 大小_Redis_10

当给定的 index索引 超出 字符串值的长度时,字符串值末尾 直到 索引index-1之间 的 部分 将使用 空字节进行填充
换句话说,这些字节的所有二进制位都会被设置为0

redis opsForset 设置大小 redisobject 大小_Redis_11

4.获取部分字符串-getrange

getrange key start end

startend分别是开始和结束的偏移量,偏移量从0开始计算,例如下面操作获取了值best的前两个字符

127.0.0.1:6379> getrange redis 0 1
"be"

时间复杂度

redis opsForset 设置大小 redisobject 大小_Redis_12

使用场景(重要!!!)

  • 缓存功能
    比较典型的缓存使用场景 是 Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取
    由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
  • 计数
    许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。例如视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1
  • 共享Session
    一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。了解决这个问题,可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取
  • 限速
    很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次