Redis 字符串对象
2023-3-24
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 

字符串对象的编码

字符串对象的编码可以是intraw或者embstr
编码
可以用 long 类型保存的整数
int
可以用 long double 类型保存的浮点数
embstr 或者 raw
字符串值, 或者因为长度太大而没办法用 long 类型表示的整数, 又或者因为长度太大而没办法用long double 类型表示的浮点数
embstr 或者 raw
 
如果一个字符串对象保存的是整数值, 并且这个整数值可以用long类型来表示, 那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long), 并将字符串对象的编码设置为int 。
如果执行以下SET命令, 那么服务器将创建一个int编码的字符串对象作为number键的值:
notion image
 
如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于39字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为 raw 。
如果执行以下命令, 那么服务器将创建一个 raw 编码的字符串对象作为 story 键的值:
notion image
 
如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于 39 字节, 那么字符串对象将使用 embstr 编码的方式来保存这个字符串值。
embstr 编码是专门用于保存短字符串的一种优化编码方式, 这种编码和 raw 编码一样, 都使用 redisObject 结构和 sdshdr 结构来表示字符串对象, 但 raw 编码会调用两次内存分配函数来分别创建 redisObject 结构和 sdshdr 结构, 而 embstr 编码则通过调用一次内存分配函数来分配一块连续的空间, 空间中依次包含 redisObject 和 sdshdr 两个结构:
notion image
embstr 编码的字符串对象在执行命令时, 产生的效果和 raw 编码的字符串对象执行命令时产生的效果是相同的, 但使用 embstr 编码的字符串对象来保存短字符串值有以下好处:
  1. embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次
  1. 释放 embstr 编码的字符串对象只需要调用一次内存释放函数, 而释放 raw 编码的字符串对象需要调用两次内存释放函数
  1. 因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势
 
以下命令创建了一个embstr编码的字符串对象作为msg键的值:
notion image
 
 
可以用long double类型表示的浮点数在Redis中也是作为字符串值来保存的: 如果要保存一个浮点数到字符串对象里面, 那么程序会先将这个浮点数转换成字符串值, 然后再保存起转换所得的字符串值。
举个例子, 执行以下代码将创建一个包含3.14的字符串表示 "3.14" 的字符串对象:
在有需要的时候, 程序会将保存在字符串对象里面的字符串值转换回浮点数值, 执行某些操作, 然后再将执行操作所得的浮点数值转换回字符串值, 并继续保存在字符串对象里面。如果执行以下代码的话:
程序首先会取出字符串对象里面保存的字符串值 "3.14" , 将它转换回浮点数值 3.14 , 然后把 3.14 和 2.0 相加得出的值 5.14 转换成字符串 "5.14" , 并将这个 "5.14" 保存到字符串对象里面。
 

编码的转换

int 编码的字符串对象和 embstr 编码的字符串对象在条件满足的情况下, 会被转换为 raw 编码的字符串对象。
对于 int 编码的字符串对象来说, 如果向对象执行了一些命令, 使得这个对象保存的不再是整数值, 而是一个字符串值, 那么字符串对象的编码将从 int 变为 raw 。
 
通过 APPEND 命令, 向一个保存整数值的字符串对象追加了一个字符串值, 因为追加操作只能对字符串值执行, 所以程序会先将之前保存的整数值 10086 转换为字符串值 "10086" , 然后再执行追加操作, 操作的执行结果就是一个 raw 编码的、保存了字符串值的字符串对象:
另外, 因为 Redis 没有为 embstr 编码的字符串对象编写任何相应的修改程序 (只有 int 编码的字符串对象和 raw 编码的字符串对象有这些程序), 所以 embstr 编码的字符串对象实际上是只读的: 当我们对 embstr 编码的字符串对象执行任何修改命令时, 程序会先将对象的编码从 embstr 转换成 raw , 然后再执行修改命令; 因为这个原因, embstr 编码的字符串对象在执行修改命令之后, 总会变成一个 raw 编码的字符串对象。
一个 embstr 编码的字符串对象在执行 APPEND 命令之后, 对象的编码从embstr 变为 raw :
 
 
 

Redis 字符串命令

notion image
 
  • SET:设置指定 key 的值
  • GET:获取指定 key 的值
  • GETRANGE:返回 key 中字符串值的子字符
    • 语法格式如下:GETRANGE KEY_NAME start end
  • GETSET:将给定 key 的值设为 value ,并返回 key 的旧值 ( old value )
  • GETBIT:对 key 所储存的字符串值,获取指定偏移量上的位 ( bit )
  • MGET:获取所有(一个或多个)给定 key 的值
  • SETBIT:对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)
  • SETEX:设置 key 的值为 value 同时将过期时间设为 seconds
  • SETNX:只有在 key 不存在时设置 key 的值
  • SETRANGE:从偏移量 offset 开始用 value 覆写给定 key 所储存的字符串值
  • STRLEN:返回 key 所储存的字符串值的长度
  • MSET:同时设置一个或多个 key-value 对
  • MSETNX:同时设置一个或多个 key-value 对
  • PSETEX:以毫秒为单位设置 key 的生存时间
  • INCR:将 key 中储存的数字值增一
  • INCRBY:将 key 所储存的值加上给定的增量值 ( increment )
  • INCRBYFLOAT:将 key 所储存的值加上给定的浮点增量值 ( increment )
  • DECR:将 key 中储存的数字值减一
  • DECRBY:将 key 所储存的值减去给定的减量值 ( decrement )
  • APPEND:将 value 追加到 key 原来的值的末尾
Redis string类型提供了一些专门操作数值的命令,比如 INCRBY(自增)、DECRBR(自减)、INCR(加1) 和 DECR(减1) 等命令。
注意:此时key对应的value值是必须是一个整数,或浮点数,使用命令对这个数值进行自增或自减操作。当然,这个数值也不能无限的增大或减小, Redis规定的数值范围是 -9223372036854775808 至 9223372036854775807,如果超过了这个数值范围,Redis 就会报错
 
 

应用场景

缓存对象

使用 String 来缓存对象有两种方式:
  • 直接缓存整个对象的 JSON,命令例子: SET user:1 '{"name":"xiaolin", "age":18}'
  • 采用将 key 进行分离为 user:ID:属性,采用 MSET 存储,用 MGET 获取各属性值,命令例子: MSET user:1:name xiaolin user:1:age 18 user:2:name xiaomei user:2:age 20

常规计数

因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
比如计算文章的阅读量:

分布式锁

SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:
  • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
  • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:
  • lock_key 就是 key 键;
  • unique_value 是客户端生成的唯一的标识;
  • NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
  • PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。
这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。
 

共享 Session 信息

通常我们在开发后台管理系统时,会使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。
例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。
分布式系统单独存储 Session 流程图:
notion image
因此,我们需要借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。
分布式系统使用同一个 Redis 存储 Session 流程图:
notion image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

bitmap位图

在平时开发过程中,经常会有一些 bool 类型数据需要存取。比如记录用户一年内签到的次数,签了是 1,没签是 0。如果使用 key-value 来存储,那么每个用户都要记录 365 次,当用户成百上亿时,需要的存储空间将非常巨大。为了解决这个问题,Redis 提供了位图结构。
位图(bitmap)同样属于 string 数据类型。Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。位图结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,这大大增加了 value 存储数量,它存储上限为
 
位图本质上就是一个普通的字节串,也就是 bytes 数组。可以使用getbit/setbit命令来处理这个位数组,位图的结构如下所示:
notion image
位图适用于一些特定的应用场景,比如用户签到次数、或者登录次数等。上图是表示一位用户 10 天内来网站的签到次数,1 代表签到,0 代表未签到,这样可以很轻松地统计出用户的活跃程度。相比于直接使用字符串而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。
 
Redis 的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,位数组就会自动扩充。
设置一个名为 a 的 key,对这个 key 进行位图操作,使得 a 的对应的 value 变为“he”。分别获取字符“h”和字符“e”的八位二进制码,如下所示:
接下来,只要对需值为 1 的位进行操作即可:
notion image
把 h 和 e 的二进制码连接在一起,第一位的下标是 0,依次递增至 15,然后将数字为 1 的位置标记出来,得到 1/2/4/9/10/13/15,把这组数字称为位的“偏置数”,最后按照上述偏置数对字符 a 进行位图操作。
 
位图常用命令:SETBIT命令用来设置或者清除某一位上的值、GETBIT命令用来获取某一位上的值、BITCOUNT命令统计指定位区间上,值为 1 的个数
 
  • Redis
  • 对象类型和编码Redis 列表对象
    目录