Redis(5)—— 字符串

2.2 字符串
字符串类型是Redis最基础的数据结构。 首先键都是字符串类型, 而且其他几种数据结构都是在字符串类型基础上构建的, 所以字符串类型能为其他四种数据结构的学习奠定基础。 如图2-7所示, 字符串类型的值实际可以是字符串( 简单的字符串、 复杂的字符串( 例如JSON、 XML) ) 、 数字( 整数、 浮点数) , 甚至是二进制( 图片、 音频、 视频) , 但是值最大不能超过512MB。

2.2.1 命令
字符串类型的命令比较多, 本小节将按照常用和不常用两个维度进行说明, 但是这里常用和不常用是相对的, 希望读者尽可能都去了解和掌握。
1.常用命令
( 1) 设置值

set key value [ex seconds] [px milliseconds] [nx|xx]
下面操作设置键为hello, 值为world的键值对, 返回结果为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选项是一样的。 下面的例子说明了set、 setnx、 setxx的区别。
当前键hello不存在:
127.0.0.1:6379> exists hello
(integer) 0
设置键为hello, 值为world的键值对:
127.0.0.1:6379> set hello world
OK
因为键hello已存在, 所以setnx失败, 返回结果为0:
127.0.0.1:6379> setnx hello redis
(integer) 0
因为键hello已存在, 所以set xx成功, 返回结果为OK:
127.0.0.1:6379> set hello jedis xx
OK
setnx和setxx在实际使用中有什么应用场景吗? 以setnx命令为例子, 由于Redis的单线程命令处理机制, 如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功, setnx可以作为分布式锁的一种实现方案, Redis官方给出了使用setnx实现分布式锁的方法: http://redis.io/topics/distlock。
( 2) 获取值
get key
下面操作获取键hello的值:
127.0.0.1:6379> get hello
"world"
如果要获取的键不存在, 则返回nil( 空) :
127.0.0.1:6379> get not_exist_key
(nil)
( 3) 批量设置值
mset key value [key value ...]
下面操作通过mset命令一次性设置4个键值对:
127.0.0.1:6379> mset a 1 b 2 c 3 d 4
OK
( 4) 批量获取值
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命令需要按照图2-8的方式来执行, 具体耗时如下:

n次get时间 = n次网络时间 + n次命令时间


使用mget命令后, 要执行n次get命令操作只需要按照图2-9的方式来完成, 具体耗时如下:
n次get时间 = 1次网络时间 + n次命令时间

Redis可以支撑每秒数万的读写操作, 但是这指的是Redis服务端的处理能力, 对于客户端来说, 一次命令除了命令时间还是有网络时间, 假设网络时间为1毫秒, 命令时间为0.1毫秒( 按照每秒处理1万条命令算) , 那么执行1000次get命令和1次mget命令的区别如表2-1, 因为Redis的处理能力已经足够高, 对于开发人员来说, 网络可能会成为性能的瓶颈。

学会使用批量操作, 有助于提高业务处理效率, 但是要注意的是每次批量操作所发送的命令数不是无节制的, 如果数量过多可能造成Redis阻塞或者网络拥塞。
( 5) 计数
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
很多存储系统和编程语言内部使用CAS机制实现计数功能, 会有一定的CPU开销, 但在Redis中完全不存在这个问题, 因为Redis是单线程架构, 任何命令到了Redis服务端都要顺序执行。
2.不常用命令
( 1) 追加值

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 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 key value
getset和set一样会设置值, 但是不同的是, 它同时会返回键原来的值,例如:
127.0.0.1:6379> getset hello world
(nil)
127.0.0.1:6379> getset hello redis
"world"
( 4) 设置指定位置的字符
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"
( 5) 获取部分字符串
getrange key start end
start和end分别是开始和结束的偏移量, 偏移量从0开始计算, 例如下面操作获取了值best的前两个字符。
127.0.0.1:6379> getrange redis 0 1
"be"
表2-2是字符串类型命令的时间复杂度, 开发人员可以参考此表, 结合自身业务需求和数据大小选择适合的命令。

2.2.2 内部编码
字符串类型的内部编码有3种:
·int: 8个字节的长整型。
·embstr: 小于等于39个字节的字符串。
·raw: 大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
整数类型示例如下:
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"
短字符串示例如下:
#小于等于39个字节的字符串: embstr
127.0.0.1:6379> set key "hello,world"
OK
127.0.0.1:6379> object encoding key
"embstr"
长字符串示例如下:
#大于39个字节的字符串: raw
127.0.0.1:6379> set key "one string greater than 39 byte........."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key

(integer) 40


2.2.3 典型使用场景
1.缓存功能

图2-10是比较典型的缓存使用场景, 其中Redis作为缓存层, MySQL作为存储层, 绝大部分请求的数据都是从Redis中获取。 由于Redis具有支撑高并发的特性, 所以缓存通常能起到加速读写和降低后端压力的作用。下面伪代码模拟了图2-10的访问过程:

1) 该函数用于获取用户的基础信息:
UserInfo getUserInfo(long id){
...
}
2) 首先从Redis获取用户信息:
// 定义键
userRedisKey = "user:info:" + id;
// 从Redis获取值
value = redis.get(userRedisKey);
if (value != null) {
// 将值进行反序列化为UserInfo并返回结果
userInfo = deserialize(value);
return userInfo;
}
开发提示
与MySQL等关系型数据库不同的是, Redis没有命令空间, 而且也没有对键名有强制要求( 除了不能使用一些特殊字符) 。 但设计合理的键名, 有利于防止键冲突和项目的可维护性, 比较推荐的方式是使用“业务名: 对象名: id: [属性]”作为键名( 也可以不是分号) 。 例如MySQL的数据库名为vs, 用户表名为user, 那么对应的键可以用"vs: user: 1", "vs: user: 1:name"来表示, 如果当前Redis只被一个业务使用, 甚至可以去掉“vs: ”。 如果键名比较长, 例如“user: {uid}: friends: messages: {mid}”, 可以在能描述键含义的前提下适当减少键的长度, 例如变为“u: {uid}: fr: m:{mid}”, 从而减少由于键过长的内存浪费。
3) 如果没有从Redis获取到用户信息, 需要从MySQL中进行获取, 并将结果回写到Redis, 添加1小时( 3600秒) 过期时间:
// 从MySQL获取用户信息
userInfo = mysql.get(id);
// 将userInfo序列化, 并存入Redis
redis.setex(userRedisKey, 3600, serialize(userInfo));
// 返回结果
return userInfo
整个功能的伪代码如下:
UserInfo getUserInfo(long id){
userRedisKey = "user:info:" + id
value = redis.get(userRedisKey);
UserInfo userInfo;
if (value != null) {
userInfo = deserialize(value);
} else {
userInfo = mysql.get(id);
if (userInfo != null)
redis.setex(userRedisKey, 3600, serialize(userInfo));
}r
eturn userInfo;
}
2.计数
许多应用都会使用Redis作为计数的基础工具, 它可以实现快速计数、查询缓存的功能, 同时数据可以异步落地到其他数据源。 例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件, 用户每播放一次视频, 相应的视频播放数就会自增1:
long incrVideoCounter(long id) {
key = "video:playCount:" + id;
return redis.incr(key);
}
开发提示
实际上一个真实的计数系统要考虑的问题会很多: 防作弊、 按照不同维度计数, 数据持久化到底层数据源等。
3.共享Session
如图2-11所示, 一个分布式Web服务将用户的Session信息( 例如用户登录信息) 保存在各自服务器中, 这样会造成一个问题, 出于负载均衡的考虑, 分布式服务会将用户的访问均衡到不同服务器上, 用户刷新一次访问可能会发现需要重新登录, 这个问题是用户无法容忍的。

为了解决这个问题, 可以使用Redis将用户的Session进行集中管理, 如图2-12所示, 在这种模式下只要保证Redis是高可用和扩展性的, 每次用户更新或者查询登录信息都直接从Redis中集中获取。

4.限速
很多应用出于安全的考虑, 会在每次进行登录时, 让用户输入手机验证码, 从而确定是否是用户本人。 但是为了短信接口不被频繁访问, 会限制用户每分钟获取验证码的频率, 例如一分钟不能超过5次, 如图2-13所示。

此功能可以使用Redis来实现, 下面的伪代码给出了基本实现思路:
phoneNum = "138xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
// 通过
}else{
// 限速
}
上述就是利用Redis实现了限速功能, 例如一些网站限制一个IP地址不能在一秒钟之内访问超过n次也可以采用类似的思路。
除了上面介绍的几种使用场景, 字符串还有非常多的适用场景, 开发人员可以结合字符串提供的相应命令充分发挥自己的想象力。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值