Redis String类型

String 类型是 Redis 最基本的数据类型,String 类型在 Redis 内部使用动态长度数组实现,Redis 在存储数据时会根据数据的大小动态地调整数组的长度。Redis 中字符串类型的值最大可以达到 512 MB。

关于字符串需要特别注意∶

  1. 首先,Redis 中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4种数据结构的学习奠定基础。
  2. 其次,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似 JSON 、XML 格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过 512 MB。

字符串数据类型

由于 Redis 内部存储字符串完全是按照二进制流的形式保存的,所以 Redis 是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。

String常见命令

set和get

set

将string类型的value设置到key中。如果key之前存在,则覆盖,无论原来的数据类型是什么。之前关于此key的TTL也全部失效。

语法:

sql 复制代码
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

时间复杂度:O(1)

选项: SET命令支持多种选项来影响它的行为:

  • EX seconds ------使用秒作为单位设置key的过期时间。
  • PX milliseconds------使用毫秒作为单位设置key的过期时间。
  • NX ------只在key不存在时才进行设置,即如果key之前已经存在,设置不执行(返回nil)。
  • XX ------只在key存在时才进行设置(相当于更新key),即如果key之前不存在,设置不执行。

注意:由于带选项的SET命令可以被SETNXSETEXPSETEX等命令代替,所以之后的版本中,Redis可能进行合并。

返回值

  • 如果设置成功,返回OK
  • 如果由于SET指定了NX或者XX但条件不满足,SET不会执行,并返回(nil)

示例

sql 复制代码
redis> exists mykey
(integer) 0
redis> set mykey "Hello"
OK
redis> get mykey
"Hello"
redis> set mykey "World" NX
(nil)
redis> del mykey
(integer) 1
redis> exists mykey
(integer) 0
redis> set mykey "World" XX
(nil)
redis> get mykey
(nil)
redis> set mykey "World" NX
OK
redis> get mykey
"World"
redis> set mykey "Will expire in 10s" EX 10
OK
redis> get mykey
"Will expire in 10s"
redis> get mykey # 10秒之后
(nil)

get

获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。

语法

sql 复制代码
GET key

时间复杂度:O(1)

返回值:key对应的value,当key不存在,返回nil。

示例

sql 复制代码
redis> get nonexisting
(nil)
redis> set mykey "Hello"
"OK"
redis> get mykey
"Hello"
redis> del mykey
(integer) 1
redis> exists mykey
(integer) 0
redis> hset mykey name Bob   #创建一个哈希类型
(integer) 1
redis> get mykey   #获取非string类型出现报错
(error) WRONGTYPE Operation against a key holding the wrong kind of value

mget和mset

mget

一次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil。

语法

sql 复制代码
MGET key [key ...]

时间复杂度:O(N) N是key数量

返回值:对应value的列表

示例

sql 复制代码
redis> set key1 "hello"
OK
redis> set key2 "world"
OK
redis> mget key1 key2 nonexisting
1) "hello"
2) "world"
3) (nil)

mset

MSET命令用于同时设置一个或多个键值对。这个命令会覆盖已存在的键值对。使用MSET能够优化网络性能,因为一次的网络请求就可以完成多个键值对的设置,而不是逐个发送SET命令。

语法

sql 复制代码
MSET key value [key value ...]

时间复杂度:O(N) N 是 key 数量

返回值:永远是 OK

示例

sql 复制代码
redis> mset key1 "hello" key2 "world"
OK
redis> mget key1 key2
1) "hello"
2) "world

多次 get vs 单次 mget

使用 mget / mset 由于可以有效地减少了网络时间,所以性能相较更高。

学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致Redis阻塞。

setnx,setex,psetex

setnx

SETNX命令用于设置一个键值对,但是只有当这个键不存在的时候才会进行设置,如果键已经存在,那么SETNX不会做任何操作

语法

sql 复制代码
SETNX key value

时间复杂度:O(1)

返回值:1 表示设置成功。0 表示没有设置。

示例

sql 复制代码
redis> setnx mykey "hello"
(integer) 1
redis> setnx mykey "world"
(integer) 0
redis> get mykey
"hello"

setex

该命令用于设置键值对,并指定该键值对的生存时间(TTL,Time-To-Live)。在指定的时间后,Redis将自动删除该键值对。

语法

sql 复制代码
SETEX key seconds value

时间复杂度:O(1)

返回值:成功返回OK,执行遇到错误,(例如,设置的生存时间(TTL)不是数字或者 TTL 为负数),Redis 将返回一个出错信息。

示例

sql 复制代码
redis> setex session 10 "abc123"
OK
redis> get session
"abc123"
redis> ttl session
(integer) 5
redis> get session  #10s之后
(nil)

redis> setex session -1 "hello"
(error) ERR invalid expire time in setex   #出现错误,返回错误信息

psetex

psetex和setex的用法相同,唯一不同的是psetex的单位是毫秒。

计数命令

incr和incrby

incr:将key对应的string表示的数字加一。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法

sql 复制代码
INCR key

时间复杂度:O(1)

返回值:integer类型的加完后的数值。

示例

sql 复制代码
redis> exists mykey
(integer) 0
redis> incr mykey
(integer) 1
redis> set mykey "10"
"OK"
redis> incr mykey
(integer) 11
redis> set mykey "234293482390480948029348230948"
"OK"
redis> incr mykey
(error) value is not an integer or out of range
redis> set mykey 'not a number'
"OK"
redis> incr mykey
(error) value is not an integer or out of range

incrby:将key对应的string表示的数字加上对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法

sql 复制代码
INCRBY key decrement

时间复杂度:O(1)

返回值:integer 类型的加完后的数值。

示例

sql 复制代码
redis> exists mykey
(integer) 0
redis> incrby mykey 3
(integer) 3
redis> set mykey "10"
"OK"
redis> incrby mykey 3
(integer) 13
redis> incrby mykey "not a number"
(error) ERR value is not an integer or out of range
redis> set mykey "234293482390480948029348230948"
"OK"
redis> incrby mykey 3
(error) value is not an integer or out of range
redis> set mykey 'not a number'
"OK"
redis> incrby mykey 3
(error) value is not an integer or out of range

decr和decrby

decr:将key对应的string表示的数字减一。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法

sql 复制代码
DECR key

时间复杂度:O(1)

返回值:integer 类型的减完后的数值。

示例

sql 复制代码
redis> exists mykey
(integer) 0
redis> decr mykey
(integer) -1
redis> set mykey "10"
"OK"
redis> decr mykey
(integer) 9
redis> set mykey "234293482390480948029348230948"
"OK"
redis> decr mykey
(error) value is not an integer or out of range
redis> set mykey 'not a number'
"OK"
redis> decr mykey
(error) value is not an integer or out of range

decrby:将key对应的string表示的数字减去对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错。

语法

sql 复制代码
DECRBY key decrement

时间复杂度:O(1)

返回值:integer 类型的减完后的数值。

示例

bash 复制代码
redis> exists mykey
(integer) 0
redis> decrby mykey 3
(integer) -3
redis> set mykey "10"
"OK"
redis> decrby mykey 3
(integer) 7
redis> decrby mykey "not a number"
(error) ERR value is not an integer or out of range
redis> set mykey "234293482390480948029348230948"
"OK"
redis> decrby mykey 3
(error) value is not an integer or out of range
redis> set mykey 'not a number'
"OK"
redis> decrby mykey 3
(error) value is not an integer or out of range

incrbyfloat

将key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key不存在,则视为key对应的value是0。如果key对应的不是string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。

语法

sql 复制代码
INCRBYFLOAT key increment

时间复杂度:O(1)

返回值:加/减完后的数值。

示例

sql 复制代码
redis> set mykey 10.50
"OK"
redis> incrbyfloat mykey 0.1
"10.6"
redis> incrbyfloat mykey -5
"5.6"
redis> set mykey 5.0e3    #5000
"OK"
redis> incrbyfloat mykey 2.0e2  #200
"5200"

很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。

其他命令

append

如果key已经存在并且是一个string,命令会将value追加到原有string 的后边。如果key不存在,则效果等同于SET命令。

语法

sql 复制代码
APPEND KEY VALUE

时间复杂度:O(1) 追加的字符串一般长度较短, 可以视为 O(1)

返回值:追加完成之后 string 的长度。注意这里的长度单位是字节

redis的字符串,不会对字符编码做任何处理,也就是说redis不认识字符,只认识字节

示例

sql 复制代码
redis> exists mykey
(integer) 0
redis> append mykey "Hello"
(integer) 5
redis> get mykey
"Hello"
redis> append mykey " World"
(integer) 11
redis> get mykey
"Hello World"

redis> append mykey 你好世界
(integer) 23
redis> get mykey
"hello world\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c"

为什么这里的输出结果"你好世界"输出的结果没有符合预期

"你好世界" 这个字符串是由 4 个 UTF-8 编码的汉字组成,每个汉字占用 3 个字节,总共占用 12 个字节。

当使用 GET 命令获取 "mykey" 的值时,看到的是编码后的 "你好世界" 字符串的字节序列。这个字节序列以十六进制进行了转义

如何解决呢?

在启动redis客户端的时候,加上一个--raw这样的选项,就可以使redis客户端额能够自动的把二进制数据尝试翻译。

sql 复制代码
[nullptr@bogon Code]$ redis-cli --raw
redis> flushall
OK
redis> append mykey "hello"
5
redis> append mykey " world"
11
redis> append mykey " 你好世界"
23
redis> get mykey
hello world 你好世界

getrange

返回key对应的string的子串,由start和end确定(左闭右闭)。可以使用负数表示倒数。-1代表倒数第一个字符,-2代表倒数第二个,其他的与此类似。超过范围的偏移量会根据string 的长度调整成正确的值。

语法

sql 复制代码
GETRANGE key start end

时间复杂度:O(N) N 为[start, end]区间的长度。由于string通常比较短,可以视为是O(1)

返回值: string类型的子串

示例

sql 复制代码
redis> set mykey "This is a string"
"OK"
redis> getrange mykey 0 3
"This"
redis> getrange mykey -3 -1
"ing"
redis> getrange mykey 0 -1
"This is a string"
redis> getrange mykey 10 100
"string

注意:如果字符串保存的是汉字,此时进行子串切分,很可能切出来的就不是一个完整的汉字了。

示例

sql 复制代码
127.0.0.1:6379> set mykey "你好世界"
OK
127.0.0.1:6379> get mykey
你好世界
127.0.0.1:6379> getrange mykey 0 3
你�

setrange

覆盖字符串的一部分,从指定的偏移开始。

语法

sql 复制代码
SETRANGE key offset value

时间复杂度:O(N) N为value的长度.由于一般给的value 比较短,通常视为O(1)。

返回值:替换后的string的长度。

示例

sql 复制代码
redis> set key1 "Hello World"
"OK"
redis> setrange key1 6 "Redis"
(integer) 11
redis> get key1
"Hello Redis"

注意:和getrange一样,如果当前value是一个中文字符串,进行setrange的时候,是可能出现问题的

setrange针对不存在的key也是可以操作的,不过会把offset之前的内容填充成0x00

strlen

获取key对应的string的长度(单位是字节)。当key存放的类似不是string时,报错。

语法

sql 复制代码
STRLEN key

时间复杂度:O(1)

返回值:string的长度。或者当key不存在时,返回0。

示例

bash 复制代码
redis> set mykey "hello world"
OK
redis> strlen mykey
11
redis> set mykey "你好世界"
OK
redis> strlen mykey
12

命令小结

String内部编码

字符串类型的内部编码有3种:

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

Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现。

整型类型示例如下

sql 复制代码
127.0.0.1:6379> set key 6379
OK
127.0.0.1:6379> object encoding key
"int"

短字符串示例如下

sql 复制代码
# ⼩于等于 39 个字节的字符串
127.0.0.1:6379> set key "hello"
OK
127.0.0.1:6379> object encoding key
"embstr"

长字符串示例如下

sql 复制代码
# ⼤于 39 个字节的字符串
127.0.0.1:6379> set key "one string greater than 39 bytes ........"
OK
127.0.0.1:6379> object encoding key
"raw"

String使用场景

缓存(Cache)功能

Redis + MySQL 组成的缓存存储架构

上图是比较典型的缓存使用场景,其中Redis作为缓冲层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

下面的伪代码模拟了业务数据访问过程:

1)假设业务是根据用户uid获取用户信息

cpp 复制代码
UserInfo getUserInfo(long uid) {
 ...
}

2)首先从Redis获取用户信息,我们假设用户信息保存在"user:info:"对应的键中:

cpp 复制代码
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令:get key;
// 如果缓存命中(hit)
if (value != null) {
 // 假设我们的⽤⼾信息按照 JSON 格式存储
 	UserInfo userInfo = JSON 反序列化(value);
 	return userInfo;
}

3)如果没有从Redis中得到用户信息,及缓存miss,则进一步从MySQL中获取对应的信息,随后写入缓存并返回:

cpp 复制代码
// 如果缓存未命中(miss)
if (value == null) {
 // 从数据库中,根据 uid 获取⽤⼾信息
 UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = <uid>
 
 // 如果表中没有 uid 对应的⽤⼾信息
 if (userInfo == null) {
 响应 404
 	return null;
 }
 
 // 将⽤⼾信息序列化成 JSON 格式
 String value = JSON 序列化(userInfo);
 
 // 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒) Redis 执⾏命令:set key value ex 3600
 
 // 返回⽤⼾信息
 	return userInfo;
}

通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次MySQL查询,极大地提升了查询效率,也降低了MySQL的访问数。

与MySQL等关系型数据库不同的是,Redis没有表、字段这种命名空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用"业务名:对象名:唯一标识:属性"作为键名。例如

MySQL的数据库名为vs,用户表名为user_info,那么对应的键可以使用"vs:user_info:6379"、"vs:user_info:6379:name"来表示,如果当前Redis只会被一个业务使用,可以省略业务名"vs:"。如果键名过程,则可以使用团队内部都认同的缩写替代,例如"user:6379:friends:messages:5217"可以被"u:6379🇫🇷m:5217"代替。毕竟键名过长,还是会导致 Redis的性能明显下降的。

计数(Counter)功能

许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如图所示,例如视频网站的视频播放次数可以使用Redis来完成:用户每播放一次视频,相应的视频播放数就会自增1。

cpp 复制代码
// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
 	key = "video:" + vid;
 	long count = Redis 执⾏命令:incr key
 	return counter;
 }

实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。

共享会话(Session)

如图所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

Session分散存储:

为了解决这个问题,可以使用Redis将用户的Session信息进行集中管理,如下图所示,在这种模式下,只要保证Redis是高可用和可扩展性的,无论用户被均衡到哪台Web服务器上,都集中从Redis 中查询、更新Session信息。

Redis集中管理 Session:

手机验证码

很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。

此功能可以用以下伪代码说明基本实现思路:

cpp 复制代码
String 发送验证码(phoneNumber) {
    key = "shortMsg:limit:" + phoneNumber;
    // 设置过期时间为 1 分钟(60 秒)
    // 使⽤ NX,只在不存在 key 时才能设置成功
    bool r = Redis 执⾏命令:set key 1 ex 60 nx if (r == false) {
        // 说明之前设置过该⼿机的验证码了
        long c = Redis 执⾏命令:incr key if (c > 5) {
            // 说明超过了⼀分钟 5 次的限制了
            // 限制发送
            return null;
        }
    }

    // 说明要么之前没有设置过⼿机的验证码;要么次数没有超过 5 次
    String validationCode = ⽣成随机的 6 位数的验证码();

    validationKey = "validation:" + phoneNumber;
    // 验证码 5 分钟(300 秒)内有效
    Redis 执⾏命令:set validationKey validationCode ex 300;

    // 返回验证码,随后通过⼿机短信发送给⽤⼾
    return validationCode;
}
// 验证⽤⼾输⼊的验证码是否正确
bool 验证验证码(phoneNumber, validationCode) {
    validationKey = "validation:" + phoneNumber;

    String value = Redis 执⾏命令:get validationKey;
    if (value == null) {
        // 说明没有这个⼿机的验证码记录,验证失败
        return false;
    }

    if (value == validationCode) {
        return true;
    } else {
        return false;
    }
}

以上介绍了使用Redis的字符串数据类型可以使用的几个场景,但其适用场景远不止于此,开发人员可以结合字符串类型的特点以及提供的命令,充分发挥自己的想象力,在自己的业务中去找到合适的场景去使用Redis的字符串类型。

相关推荐
码爸8 分钟前
hbase merge工具
大数据·数据库·hbase
hong_zc10 分钟前
JDBC 编程
java·数据库·mysql
Leoysq10 分钟前
Oracle 数据库常用命令与操作指南
数据库·笔记·oracle
数据与人12 分钟前
Can‘t connect to local MySQL server through socket
数据库
天蓝蓝2352815 分钟前
MySQL数据库的备份与恢复
数据库·mysql·oracle
踏浪逐行16 分钟前
数据库(mysql)常用命令
数据库·mysql
Leoysq36 分钟前
Navicate 链接Oracle 提示 Oracle Library is not loaded ,账号密码都正确地址端口也对
数据库·oracle
imc.111 小时前
初识linux(2)
java·linux·数据库
武子康1 小时前
大数据-143 - ClickHouse 集群 SQL 超详细实践记录!
java·大数据·数据库·分布式·sql·clickhouse·flink
MoFe12 小时前
【.net core】线程的创建和方法调用
服务器·数据库·.netcore