目录
一,常用数据结构
1.0 前言
1.1 string
1.2 hash
1.3 list
1.4 set
1.5 zset
1.6 演示
二,关于单线程模型
2.1 关于Redis的单线程
2.2 Redis为什么快
一,常用数据结构
1.0 前言
Redis是采用键值对的方式来存储数据的,key强制为字符串类型,value可以为其它的类型,常用的有5种:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合),当前版本的redis支持10个,但除了上面5种的其它5种只在某些特殊场景下才会用到,到时候只需要查阅文档即可~
注意:Redis在实现上述数据结构的时候,会在源码层面,针对上述实现进行特定的优化,来达到节省 时间/空间 的效果;而这个“特定的优化”,就是指Redis内部实现这些数据类型时,会用到其它数据结构的编码方式
例子:Redis承诺,我这个hash表,它进行查询插入删除等操作都保证是O(1),所以Redis的hahs内部可能是其它的数据结构,但是能达到hash的效果,并且达到O(1)
结论:对于上面的5种数据类型,只是Redis承诺给你的,它内部的编码方式是Redis自己实现的,比如我提供一个string的类型,但是它是用字符数组实现的,但是能达到string的效果。
1.1 string
数据类型 | 内部编码 |
---|---|
string | raw |
int | |
embstr |
- raw:是最基本的字符串,底层就是一个char数组(C/C++)或者byte数组(Java)
- int:Redis也可以用来实现一些“计数”这样的功能,当value就是一个整数的时候,Redis可能直接用int来存
- embstr:这个是用来针对短字符串进行的特殊优化
1.2 hash
数据类型 | 内部编码 |
---|---|
hash | hashtable |
ziplist |
- hashtable:就是Redis内部自己的哈希表的实现,虽然实现方式可能不太一样,但是整体思路和我们之前学的差不多
- ziplist:这个叫做“压缩列表”,也是一种优化策略,在Redis中,如果很多key的value是hash类型,而且hash里面元素比较少的时候,可能就被优化成ziplist了,能节省空间
1.3 list
数据类型 | 内部编码 |
---|---|
list | linklist |
ziplist |
- linklist:这个就是最基本的链表结构,双向带头循环链表
- ziplist:和上面一样,是压缩列表
上面的两种方式是老版本Redis中的实现方式,在Redis 3.2 版本后,就用quicklist来实现了
- quicklist: quicklist本体是链表,但是里面的每个元素是ziplist,同时兼顾了两种类型的优点,把空间和效率都折衷兼顾到。(该类型有点类似于C++的deque,但是Java标准库目前我没有了解到有类似的结构)
1.4 set
数据类型 | 内部编码 |
---|---|
set | hashtable |
intset |
- hashtable:和上面一样的,是Redis自己实现的哈希表
- intset:这个也是一个set,但是这个set里面存的类似都是int整数
1.5 zset
数据类型 | 内部编码 |
---|---|
zset | skiplist |
ziplist |
这个类型有点特殊,上面四种我们都可以在C++的STL中找到比较相似的容器,skiplist叫做“跳表”,这个结构很像我们之前做过的一道题很像:LCR 154. 复杂链表的复制 - 力扣(LeetCode)
跳表也是链表,不同于普通链表,它每个节点上面有多个指针域,Redis通过巧妙地搭配这些指针域地指向,就可以做到从跳表上查询元素的时间复杂度为O(logN)
1.6 演示
OBJECT encoding key #该命令可以查看value底层具体的数据类型
二,关于单线程模型
2.1 关于Redis的单线程
前面我们说Redis是单线的,其实指的是Redis只用一个线程去处理所有的命令请求,其实Redis内需也是有多个线程的,它们处理的是网络IO等。
场景:假设现在有2个客户端,同时操作Redis服务器,服务器中有键值对,如下图:
然后客户端发送incr counter命令,表示使key的value进行 +1 操作,两个客户端一起发送时就会有线程安全问题
问题:所以这两个客户端“并发”发起了上述的请求,是否意味着服务器那边也会存在类似的线程安全问题呢?
解答:并不会。因为Redis服务器实际上是单线程模型,保证了当前收到的多个请求是串行执行的,多个命令到达Redis后,也要在队列中排队,再等待服务器从里面一个一个取。
Redis能够使用单线程模型很好的工作,原因在于Redis的核心业务逻辑,都是短平快的,不太消耗CPU资源,也就不太吃多核了
弊端:Redis就必须要特别小心,如果某个操作占用时间长,就会阻塞其它命令的执行
2.2 Redis为什么快
- Redis主要操作是访问内存,MySQL是访问硬盘,访问内存快很多
- Redis的核心功能逻辑,比数据库更简单(数据库对于数据的插入删除查询,都有更复杂的功能,从而势必要花非更多的开销
- 单线程模型,避免了一些不必要的线程竞争开销,Redis的每个操作都是短平快的,都是不怎么消耗的CPU的内存数据操作
- Redis处理网络IO的时候,使用了epoll这样的IO多路复用机制
下面简单解释下第四点:
- 一个线程就可以管理多个socket,针对TCP来说,服务器这边每次要服务一个客户端,就要维护一个socket,一个服务器服务多个客户端,就要维护多个socket。
- 但是很多情况下,这些socket也不是一直在传输数据,大部分的socket大部分时间是静默的 --> 同一时刻只有少数socket是活跃的
- 我们最开始介绍TCP服务器时,是利用线程池给每个socket分配一个线程的,客户端多了,线程就多了,开销就大了,于是就有了IO多路复用,就可以用一个线程来处理socket,这是操作系统给程序员提供的机制,提供了一套API,内部的功能都是操作系统内核实现的
- Linux上提供的IO多路复用,主要是三套API,select,pool,epoll,其中epoll是运行效率最高的机制
举个例子:家里面三个人想吃小吃,三个人分别想吃“饺子”,“炒饭”,“煎饼果子”,下面有三种方案:
- 让一个人去买,先买饺子,跟老板说了之后就等着,当拿到饺子的时候再去买炒饭,煎饼果子同理(原始单线程)
- 三个人一起去,各去买自己想吃的(多线程)
- 让一个人去,先买饺子,跟老板说了之后,在老板准备饺子的期间,我直接去买炒饭和煎饼果子,然后就是三个同时在做,做好了时候老板喊我去拿,然后我就去拿 (epoll多路复用)
用第三种方法,就可以让一个线程同时做三件事,前提是 三件事交互都不频繁,大部分时间都是在等,上面标粗体的老板喊我去拿,这种有通知机制的多路复用就叫做epoll,而另一种select和上面差不多,但是“老板不会喊我”,它没有通知机制,只能“我自己来问”,效率不高
如果三件事交互是很频繁的,那还是老老实实搞多线程靠谱