
1. 数据结构和内部编码
type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是Redis对外显示的数据结构。
redis的5种数据类型:
但是redis在底层实现这些数据结构的时候会有变数,也就是内部数据结构的编码方式。
这样Redis会在合适的场景选择合适的内部编码,来进行优化。
可以使用 object encoding key
查看实际编码的方式
2. redis的单线程模型
我们知道,redis是使用单线程处理所有的命令请求的,但不是说一个redis服务器进程内部真的就只有一个线程,其实有多个线程,多线程是在处理网络IO。
Redis内部只有⼀个服务窗口,多个客⼾端按照它们达到的先后顺序被排队在窗口口前,依次接受Redis的服务,不会发⽣并发问题,这个就是Redis的单线程执⾏模型。
为什么单线程还能这么快呢?(面试题)
- 快、慢都是和其它关系型数据比的
- redis的操作是访问内存,比访问硬盘快
- redis的核心功能,比数据库的业务更简单
- redis是单线程的,避免了多线程的开销
- redis的每个操作,都是短平快的,就收简单操作一下内存,不是什么耗CPU的操作,即使使用多线程,提升也不大
- redis使用epoll处理网络IO
3. String类型
字符串类型是Redis最基础的数据类型,关于字符串需要特别注意:
- 首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的
- 字符串类型的值实际可以是字符串,数字,甚⾄是二进制流数据(最大值不能超过512MB)
3.1 常用命令
- set
将string 类型的value设置到key中。如果key之前存在,则覆盖,无论原来的数据类型是什么,之前关于此key的TTL也全部失效。
set key value [expiration EX seconds | PX milliseconds] [NX|XX]
- EX seconds:使用秒作为单位设置key的过期时间。
- PX milliseconds:使用毫秒作为单位设置key的过期时间。
- NX:只在key不存在时才进⾏设置,即如果key之前已经存在,设置不执行。(添加)
- XX:只在key存在时才进⾏设置,即如果key之前不存在,设置不执行。(修改)
由于带选项的SET命令可以被SETNX
、SETEX
、PSETEX
等命令代替,所以之后的版本中,Redis可能进行合并
介绍一个命令:
FLUSHALL
可以把redis上的所有键值对都删除(工作中慎用)
- get
获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。
get key
- mget、mset
mget:⼀次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil。
mget key [key ...]
mset:⼀次性设置多个key的值。
mset key value [key value ...]
使用 mget、mset由于可以有效地减少了⽹络时间,所以性能相较更高
所以,学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单⼀命令执行时间过长,导致Redis阻塞。
- 计数命令
incr
:将key对应的string表⽰的数字加⼀。- 如果key不存在,则视为key对应的value是0。
- 如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
incrby
:将key对应的string表⽰的数字加上对应的值。decr
:将key对应的string表⽰的数字减⼀。- 如果key不存在,则视为key对应的value是0。
- 如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
decrby
:将key对应的string表⽰的数字减上对应的值。incrbyfloat
:将key对应的string表示的浮点数加上对应的值。- 如果对应的值是负数,则视为减去对应的值。
- 如果key 不存在,则视为key对应的value是0。
- 如果key对应的不是string,或者不是⼀个浮点数,则报错。允许采⽤科学计数法表⽰浮点数。
- 其它命令
APPEND
:如果key已经存在并且是⼀个string,命令会将value追加到原有string的后边。如果key不存在,则效果等同于SET命令。
APPEND KEY VALUE
getrange
:返回key对应的string的⼦串,由start和end确定(左闭右闭)。- 可以使⽤负数表⽰倒数。-1代表倒数第⼀个字符,-2代表倒数第⼆个,其他的与此类似。
- 超过范围的偏移量会根据string的⻓度调整成正确的值。
GETRANGE key start end
setrange
:覆盖字符串的⼀部分,从指定的偏移开始
SETRANGE key offset value
当offset
大于键处字符串的当前长度时,Redis 会通过填充零字节(即 \x00)来使偏移量合适;填充的零字节是不可见字符,不会影响字符串的显示内容,但会影响字符串的实际长度和二进制表示。
strlen
:获取key对应的string的⻓度。当key存放的类似不是string时,报错。
下图对上方命令的总结(命令的效果、时间复杂度)
3.2 内部编码
字符串类型的内部编码有3种:
- int:8个字节的⻓整型
- embstr:小于等于39个字节的字符串
- raw:大于39个字节的字符串
Redis 会根据当前值的类型和⻓度动态决定使⽤哪种内部编码实现。
3.3 应用场景
- 缓存功能
缓存功能是比较典型的缓存使用场景,其中Redis作为缓冲层,MySQL作为存储层,绝⼤部分请求的数据都是从Redis中获取。由于Redis具有⽀撑⾼并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
- 与MySQL等关系型数据库不同的是,Redis没有表、字段这种命名空间,而且也没有对键名有强制要求(除了不能使⽤⼀些特殊字符)。
- 但设计合理的键名,有利于防⽌键冲突和项⽬的可维护性,比较推荐的方式是使用"业务名:对象名:唯⼀标识:属性"作为键名。
- 例如MySQL的数据库名为vs,⽤⼾表名为user_info,那么对应的键可以使用"vs:user_info:6379"、“vs:user_info:6379:name” 来表示,
- 计数功能
许多应用都会使用Redis作为计数的基础⼯具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。
当然,这个操作也可以使用数据库来完成,但是当访问量大时,数据库的服务器的压力是比较大的
如图所示,例如视频网站的视频播放次数可以使用redis来完成:用户每播放⼀次视频,相应的视频播放数就会自增1。
视频ID作为key,播放次数作为value
- 共享会话
⼀个分布式Web服务将用户的Session信息(例如⽤⼾登录信息)保存在各⾃的服务器中(如左图所示),但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常⽆法保证⽤⼾每次请求都会被均衡到同⼀台服务器上,这样当用户刷新⼀次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。
为了解决这个问题,可以使⽤Redis将用户的Session信息进⾏集中管理,如右图所⽰,在这种模式下,只要保证Redis是⾼可⽤和可扩展性的,⽆论⽤⼾被均衡到哪台Web服务器上,都集中从Redis 中查询、更新Session信息。
- 手机验证码
很多应用出于安全考虑,会在每次进⾏登录时,让⽤⼾输⼊⼿机号并且配合给⼿机发送验证码,然后让用户再次输⼊收到的验证码并进⾏验证,从⽽确定是否是⽤⼾本⼈。
为了短信接⼝不会频繁访问,会限制用户每分钟获取验证码的频率,例如⼀分钟内只能获取一次
String 发送验证码(phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟(60)
// 使⽤ NX,只在不存在 key 时才能设置成功
bool r = Redis 执⾏命令:set key 1 ex 60 nx
if (r == false) {
// 说明1分钟内设置过该⼿机的验证码了
return null;
}
// 说明之前没有设置过⼿机的验证码
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的字符串类型。