文章目录
- 2. Redis 常见数据类型
- 2.1 基础知识
- 2.1.2 数据结构和内部编码
- 2.1.3 单线程架构
- 2.2 String 字符串
- 2.2.1 常见命令
- 2.2.1.1 SET
- 2.2.1.2 GET
- 2.2.1.3 MSET:批量set
- 2.2.1.4 MGET:批量get
- 2.2.1.5 SETNX:不存在的话设置键值
2. Redis 常见数据类型
2.1 基础知识
2.1.2 数据结构和内部编码
type命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是Redis对外的数据结构。如图所示:
实际上
Redis针对每种数据结构都有自己的底层内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。如表所示:
可以看到每种数据结构都有至少两种以上的内部编码实现,例如
list数据结构包含了linkedlist和ziplist两种内部编码。同时有些内部编码,例如
ziplist,可以作为多种数据结构的内部实现,可以通过object encoding命令查询内部编码:
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> lpush mylist a b c (integer) 3 127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding mylist "quicklist" 127.0.0.1:6379>可以看到
hello对应值的内部编码是embstr,键mylist对应值的内部编码是ziplist。
Redis这样设计有两个好处:
可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令就可以改进。
例如
Redis 3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。多种内部编码实现可以在不同场景下发挥各自的优势。
例如
ziplist比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。
2.1.3 单线程架构
Redis使用了单线程架构来实现高性能的内存数据库服务,本节首先通过多个客户端命令调用的例子说明Redis单线程命令处理机制,接着分析Redis单线程模型为什么性能如此之高,最终给出为什么理解单线程模型是使用和运维Redis的关键。
- 引出单线程模型
现在开启了三个redis-cli客户端同时执行命令。
客户端1设置一个字符串键值对:
127.0.0.1:6379> set hello world客户端2对counter做自增操作:
127.0.0.1:6379> incr counter客户端3对counter做自增操作:
127.0.0.1:6379> incr counter我们已经知道从客户端发送的命令经历了:发送命令、执行命令、返回结果三个阶段,其中我们重点关注第
2步。我们所谓的
Redis是采用单线程模型执行命令的是指:虽然三个客户端看起来是同时要求Redis去执行命令的,但微观角度,这些命令还是采用线性方式去执行的,只是原则上命令的执行顺序是不确定的,但一定不会有两条命令被同步执行。如图所示,可以想象
Redis内部只有一个服务窗口,多个客户端按照它们达到的先后顺序被排队在窗口前,依次接受Redis的服务,所以两条incr命令无论执行顺序,结果一定是2,不会发生并发问题,这个就是Redis的单线程执行模型。
宏观上同时要求服务的客户端:
微观上客户端发送命令的时间有先后次序的:
Redis的单线程模型
- 为什么单线程还能这么快
通常来讲,单线程处理能力要比多线程差,例如有
10000公斤货物,每辆车的运载能力是每次200公斤,那么要50次才能完成;但是如果有50辆车,只要安排合理,只需要一次就可以完成任务。那么为什么Redis使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
- 纯内存访问。
Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。- 非阻塞
IO。Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间,如图所示。- 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同一份共享数据时带来的切换和等待消耗。
Redis使用I/O多路复用模型:
虽然单线程给
Redis带来很多好处,但还是有一个致命的问题:对于单个命令的执行时间都是有要求的。如果某个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户端的阻塞,对于
Redis这种高性能的服务来说是非常严重的,所以Redis是面向快速执行场景的数据库。
问题:
redis虽然是单线程模型,为啥效率这么高呢?速度这么快呢?首先,说他快,是和其他数据库比较得来的。
redis访问内存,数据库则是访问硬盘。
redis核心功能,比数据库的核心功能更简单。数据库对于数据的插入,删除,查询…都有更复杂的功能支持,这样的功能势必要花费更多的开销。
比如,针对插入删除,数据库中的各种约束,都会使数据库做额外的工作。
redis干的活少,提供的功能相比于mysql也是少了不少。
- 单线程模型,避免了一些不必要的线程竞争开销。
就算搞多个线程,也提升不大
redis每个基本操作,都是短平快的。就是简单操作一下内存数据,不是什么特别消耗cpu的操作。
- 处理网络
I/O的时候,使用了epoll这样的I/O多路复用机制(关于epoll可以上网搜,或者看一下《LInux高性能服务器编程》)一个线程,就可以管理多个
socket。一个服务器服务多个客户端,同时就有很多个socket。这些socket上都是无事不刻的在传输数据吗?很多情况下,每个客户端和服务器之间的通信也没那么频繁。此时这么多
socket大部分时间都是静默的,上面是没有数据需要传输的。针对
TCP来说,服务器这边每次要服务一个客户端,都需要给这个客户端安排一个socket。同一时刻,只有少数socket是活跃的。
2.2 String 字符串
字符串类型是
Redis最基础的数据类型,关于字符串需要特别注意:
首先
Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的。例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他
4种数据结构的学习奠定基础。其次,如图所示,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似
JSON、XML格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据。例如图片、音频、视频等。不过一个字符串的最大值不能超过
512MB。因为redis是单线程模型,希望进行的动作比较快速。
字符串数据类型:
由于
Redis内部存储字符串完全是按照二进制流的形式保存的,所以Redis是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。
2.2.1 常见命令
2.2.1.1 SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,无论原来的数据类型是什么。之前关于此 key 的 TTL 也全部失效。
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]命令有效版本:
1.0.0之后时间复杂度:
O(1)选项:
SET命令支持多种选项来影响它的行为:EX seconds—— 使用秒作为单位设置key的过期时间。PX milliseconds—— 使用毫秒作为单位设置key的过期时间。NX—— 只在key不存在时才进行设置,即如果key之前已经存在,设置不执行。XX—— 只在key存在时才进行设置,即如果key之前不存在,设置不执行。
注意:由于带选项的
SET命令可以被SETNX、SETEX、PSETEX等命令代替,所以之后的版本中,Redis可能进行合并。返回值:
- 如果设置成功,返回
OK。- 如果由于
SET指定了NX或者XX但条件不满足,SET不会执行,并返回 (nil)。
示例:
root@yudukai:~# redis-cli 127.0.0.1:6379> exists mykey (integer) 0 127.0.0.1:6379> set mykey "Hello" OK 127.0.0.1:6379> get mykey "Hello" 127.0.0.1:6379> set mykey "World" NX # 不存在时设置 (nil) 127.0.0.1:6379> DEL mykey (integer) 1 127.0.0.1:6379> exists mykey (integer) 0 127.0.0.1:6379> set mykey "World" XX # 存在时设置 (nil) 127.0.0.1:6379> get mykey (nil) 127.0.0.1:6379> set mykey "World" NX # 不存在时设置 OK 127.0.0.1:6379> get mykey "World" 127.0.0.1:6379> set mykey "Will expire in 10s" EX 10 OK 127.0.0.1:6379> get mykey "Will expire in 10s" 127.0.0.1:6379> get mykey # 10s后 (nil) 127.0.0.1:6379>
redis文档给出的语法格式说明:
[]相当于一个独立的单元,表示可选项(可有可无的)其中
|表示"或者”的意思,多个只能出现一个
[]和[]之间,是可以同时存在的。
如果
key不存在,创建新的键值对。如果
key存在,则是让新的value覆盖旧的value,可能会改变原来的数据类型。原来这个
key的ttI(生存时间)也会失效。
高危命令:
FLUSHALL:可以删掉Redis上的所有值。初学阶段可以玩一下,公司环境玩的话,就是年终奖消失术了。
2.2.1.2 GET
获取
key对应的value。如果key不存在,返回nil。如果
value的数据类型不是string,会报错。
语法:
GET key命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
key对应的value,或者nil当key不存在。
示例:
127.0.0.1:6379> get nonexisting (nil) 127.0.0.1:6379> set mykey "hello" OK 127.0.0.1:6379> get mykey "hello" 127.0.0.1:6379> del mykey (integer) 1 127.0.0.1:6379> exists mykey (integer) 0 127.0.0.1:6379> hset mykey name Bob (integer) 1 127.0.0.1:6379> get mykey (error) WRONGTYPE Operation against a key holding the wrong kind of value # 告诉我们类型有问题 127.0.0.1:6379>2.2.1.3 MSET:批量set
一次性设置多个
key的值。
MSET key value [key value ...]命令有效版本:
1.0.1之后时间复杂度:
O(N),N是key数量返回值:永远是
OK
示例:
127.0.0.1:6379> mset key1 "hello" key2 "world" OK 127.0.0.1:6379> get key1 "hello" 127.0.0.1:6379> get key2 "world" 127.0.0.1:6379>2.2.1.4 MGET:批量get
一次性获取多个
key的值。如果对应的
key不存在或者对应的数据类型不是string,返回nil。
语法:
MGET key [key ...]命令有效版本:
1.0.0之后时间复杂度:
O(N),N是key数量返回值:对应
value的列表
示例:
127.0.0.1:6379> set key1 "hello" OK 127.0.0.1:6379> set key2 "world" OK 127.0.0.1:6379> mget key1 key2 nothing 1) "hello" 2) "world" 3) (nil) 127.0.0.1:6379>多次getvs 单次mget:
如上图所示,使用
mget/mset由于可以有效地减少了网络时间,所以性能相较更高。假设网络耗时
1毫秒,命令执行时间耗时0.1毫秒,则执行时间如下表所示。
1000次get和1次mget对比:
| 操作 | 时间 |
|---|---|
1000次get | 1000 x 1 + 1000 x 0.1 = 1100 毫秒 |
1次mget 1000个键 | 1 x 1 + 1000 x 0.1 = 101 毫秒 |
学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致
Redis阻塞。例如,你一次性设置一百万个键值对。
2.2.1.5 SETNX:不存在的话设置键值
设置
key-value,但只允许在key之前不存在的情况下。
语法:
SETNX key value命令有效版本:
1.0.0之后时间复杂度:
O(1)返回值:
1表示设置成功。0表示没有设置。
示例:
127.0.0.1:6379> SETNX mykey "Hello" # 不存在,可以设置 (integer) 1 127.0.0.1:6379> SETNX mykey "world" # 存在,不可以设置 (integer) 0 127.0.0.1:6379> get mykey "Hello" 127.0.0.1:6379>SET、SET NX、SET XX执行流程:
SETNX:不存在才能设置,存在则设置失败。
SETEX:设置key的过期时间,单位是秒
PSETEX:设置key的过期时间,单位是毫秒