Redis(开发与运维):12---字符串对象
程序员文章站
2022-05-19 14:46:19
...
一、字符串对象概述
- 字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础
- 字符串就是一个由字节组成的序列
- Redis的字符串底层实现原理可以见文章:
-
如下图所示,字符串类型的值实际可以是:
- 字符串(简单的字符串、复杂的字符串(例如JSON、XML))
- 数字 (整数、浮点数)
- 二进制(图片、音频、视频),但是值最大不能超过512MB
二、命令
设置值(SET、SETEX、SETNX)
- set:设置值。参数如下:
- ex seconds:为键设置秒级过期时间
- px milliseconds:为键设置毫秒级过期时间
- nx:键必须不存在,才可以设置成功,用于添加
- xx:与nx相反,键必须存在,才可以设置成功,用于更新
set key value [ex seconds] [px milliseconds] [nx|xx]
- setex、setnx:它们的作用和set命令的ex和nx选项是一样的
setex key seconds value setnx key value
- setnx和setxx在实际使用中有什么应用场景吗?
- 以setnx命令为例子,由于 Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value, 根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock
获取值(GET)
get key
- 如果要获取的键不存在,则返回nil(空)
批量设置值(MSET)
mset key value [key value ...]
- 例如下面依次设置4个键值对:
批量获取值(MGET)
- 如果有些键不存在,那么它的值为nil(空)
mget key [key ...]
- 例如下面获取键为a、b、c、d、e的值,其中e键不存在
批量操作的好处
- 批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次get命令需要耗时如下:
n次get时间 = n次网络时间 + n次命令时间
- 使用mget命令后,要执行n次get命令操作只需要耗时:
n次get时间 = 1次网络时间 + n次命令时间
- Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间,假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次get命令和1次mget命令的区别如下图所示,因为Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性能的瓶颈
- 学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批 量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或 者网络拥塞。
字符串的自增命令和自减命令
- 概念:
- 用户可以通过给定一个任意的数值,对存储着整数或者浮点数的字符串执行自增(increment)、自减操作(decrement)
- 在需要的时候,Redis还会将整数转换为浮点数
- 整数的取值范围和系统的长整数(long integer)的取值范围相同(32位系统中就是32位有符号整数;64位系统中就是64位有符号整数)
- 而浮点数的取值范围和精度则与IEEE 754标准的双精度浮点数(double)相同
- 返回值:
- INCR:返回增加后键的值。返回值分为三种:
- DECR:返回删除后键的值
- INCRBY:返回增加后键的值
- DECRBY:返回删除后键的值
- INCRBYFLOAT:返回增加后键的值
- 注意事项:
- 如果对一个不存在的键或者一个保存了空串的键执行自增或者自减操作,那么Redis在执行操作时会将这个键的值作为0来处理
- 如果所操作的字符串不是一个能被解释为整数或者浮点数的字符串,那么这些命令的操作将返回一个错误
- 很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任 何命令到了Redis服务端都要顺序执行
演示案例如下:
不常用命令
- STRLEN:获取字符串长度(备注:中文占3个字节)
strlen key
- GETSET:设置并返回原值。getset和set一样会设置值,但是不同的是,它同时会返回键原来的值
getset key value
- 下面给出了其他一些演示案例:
- 注意SETRANGE的用法:
- 下图是字符串类型命令的时间复杂度:
三、内部编码
-
字符串类型的内部编码有3种:
- int:8个字节的长整型
- embstr:小于等于39个字节的字符串
- raw:大于39个字节的字符串
演示说明
- Redis会根据当前值的类型和长度决定使用哪种内部编码实现
- 整数类型示例如下:
- 短字符串示例如下:
- 长字符串示例如下:
四、典型使用场景
①缓存功能
- 下图是比较典型的缓存使用场景,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
- 下面伪代码模拟了上图的访问过程:
//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; } //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)); } 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}”,从而减少由于键过长的内存浪费
②计数
- 许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源
- 例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1。代码如下所示
long incrVideoCounter(long id) { key = "video:playCount:" + id; return redis.incr(key); }
- 开发提示:实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维 度计数,数据持久化到底层数据源等
③共享Session
- 如下图所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的
- 为了解决这个问题,可以使用Redis将用户的Session进行集中管理,如下图所示,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取
④限速
- 很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证 码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率,例如一分钟不能超过5次,如下图所示
- 此功能可以使用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次也可以采用类似的思路
- 除了上面介绍的几种使用场景,字符串还有非常多的适用场景,开发人 员可以结合字符串提供的相应命令充分发挥自己的想象力
推荐阅读
-
《Redis开发与运维》读书笔记
-
Redis(开发与运维):16---有序集合对象
-
Redis(开发与运维):35---复制之(复制的建立/断开/切换主节点、安全性/只读/传输延迟、复制拓扑)
-
Redis(开发与运维):43---Sentinel之(哨兵的安装与部署、哨兵配置参数、部署技巧、哨兵API)
-
Redis(开发与运维):15---集合对象
-
Redis(开发与运维):14---列表对象
-
Redis(开发与运维):25---常用功能之(GEO(地理信息定位))
-
Redis(开发与运维):07---键的生存与过期时间(EXPIRE、EXPIREAT、TTL、PERSIST、SETEX)
-
Redis(开发与运维):24---常用功能之(发布与订阅)
-
Redis(开发与运维):23---常用功能之(HyperLogLog)