一、字符串对象概述
字符串类型是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:设置键的时候同时设置过期时间
setnx:键必须不存在,才可以设置成功,否则出错
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阻塞或 者网络拥塞。
字符串的自增命令和自减命令:
命令 | 用例和描述 |
INCR | INCR key-name 将键存储的值加上l |
DECR | DECR key-name 将键存储的值减去Ⅰ |
INCRBY | INCRBY key-name amount 将键存储的值加上整数amount |
DECRBY | DECRBY key-name amount 将键存储的值减去整数amount |
INCRBYELOAT | 工NCRBYFLOAT key-name amount 将键存储的值加上浮点数amount,这个命令在Redis 2.6或以上的版本可用 |
概念:
用户可以通过给定一个任意的数值,对存储着整数或者浮点数的字符串执行自增(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的用法:
下图是字符串类型命令的时间复杂度:
命令
时间复杂度
set key value
o(l)
get key
o(1)
del key [key ...]
o(k),k是键的个数
mset key value [ key value ...]
O(k),t是键的个致
mget key [key ...]
o(),I是键的个致
incr key
o1)
decr key
o(1)
incrby key increment
o(1)
decrby key decrement
o(1)
incrbyfloat key increment
o(1)
append key value
o(1)
strlen key
o(1)
setrange key offset value
o(1)
getrange key start end
oo),n是宁符串长度,由于获取字符串非常快,所以
如果字符非不是很长,可以视阿为O(1)
三、内部编码
字符串类型的内部编码有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序列化,并存入Redisredis.setex(userRedisKey, 3600, serialize(userInfo));// 返回结果return userInfo
整个功能的伪代码如下:
UserInfo getUserInfo(long id){userRedisKey = "user:info:" + idvalue = 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;}
应用场景1:缓存热门图片
set redis-log.jpg redis-log-data
应用场景2:存储文章。当用户想在博客中撰写一篇新文章的时候,程序就需要把文章的标题、内容、作者、发表时间等多 项信息存储起来,并在用户阅读文章的时候取出来这些信息。可以使用 mset mget msetnx 命令来进行
开发提示:
与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作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源
应用场景1:文章长度计数功能、文章摘要、文章计数
文章长度:STRLEN article:10086:content
文章摘要:GETRANGE article:10086:content 0 5
文章阅读计数:INCRBY article:10086:count
应用场景2:例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1。代码如下所示
long incrVideoCounter(long id) {
key = "video:playCount:" + id;
return redis.incr(key);
}
开发提示:实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维度计数,数据持久化到底层数据源等
③共享Session
如下图所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的
为了解决这个问题,可以使用Redis将用户的Session进行集中管理,如下图所示,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取
④限速器
应用场景1:很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证 码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率,例如一分钟不能超过5次,如下图所示
此功能可以使用Redis来实现,下面的伪代码给出了基本实现思路:
phoneNum = "138xxxxxxxx";key = "shortMsg:limit:" + phoneNum;//SET key value EX 60 NXisExists = redis.set(key,1,"EX 60","NX");if(isExists != null || redis.incr(key) <=5){// 通过}else{// 限速}
应用场景2:防止用户的账号遭到暴力破解,如果同个账号连续好几次输入错误的密码,则限制账号的登录,只 能等 30 分钟后再次登录,比如设置 3 次
1)SET max:execute:times 3
2)密码出错时 DECR max:execute:times
3)当 max:execute:times 的值小于 0 时则禁止登录,并可以设置 SETEX login:error:darren 1800 "Incorrect password",然后使用 TTL login:error:darren 1800 检测对应剩余的时间
应用场景3:例如一些网站为了防止网页内容被网络爬虫疯狂抓取,限制一个IP地址在固定的时间段内能够访问的页面数量.