七、原理解析
1. 数据结构
1.1 动态字符串SDS
为什么预分配?
Linux分成了用户空间和内存空间的,应用程序无法直接操作硬件,需要和Linux内核进行交互,用户态切换到内核态,申请内存
学习 Linux 时,经常可以看到两个词:User space(用户空间)和 Kernel space(内核空间)。
简单说,Kernel space 是 Linux 内核的运行空间,User space 是用户程序的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。
注:虚拟内存被操作系统划分成两块:内核空间和用户空间,内核空间是内核代码运行的地方,用户空间是用户程序代码运行的地方。当进程运行在内核空间时就处于内核态,当进程运行在用户空间时就处于用户态。
1.2 InSet
问题: 上图的 5 10 20 都可以用一个字节表示 为什么要采用占两个字节的(INTSET_ENC_INT16)?\
为了方便inset基于数组角标去寻找, C语言基于指针(8字节大小的整数,映射到内存空间【类似内存地址】)查找
起始地址 + (元素大小 * 角标)
1.3 Dict
using namespace std;
#define Start 1
int main()
{
for (int i = Start; i <=9 ; ++i) {
for (int j = 1; j <= i; ++j) {
cout << "|" << j << "*" << i << "=" << i*j << "| ";
}
cout << endl;
}
return 0;
}
1.4 ZipList
1、SDS 动态字符串
优点解析
- 二进制安全
C语言的字符串[0个字节的无符号指针指向的char数组]是根据 \0
结尾 内容无法容纳\0
而SDS 是根据 len 长度判断字符串的 而扩容时不包括 \0
且C的字符串每次更改都是新的常量池
2、InSet
有序/内容倒序扩容 为了扩容(或者说扩容编码长度)防止先放比较小的数据 伸展(右边补0)导致后面准备放其他数据的字节位被占满,也就是先将大得数据放置到准备的位置 而扩容前将要添加的数据在扩容(改变编码 )之后添加
注意 判断的数据大小是所占用的比特位 也就是所负数所占用的字节是可以比正数大的,正数插入元素角标位置不变,负数向后移动一位
类似于TreeSet
根据len 可以根据公式 startIndex + (index * typesize)
很快找到前后元素
适用于数据量少 多了便会慢 数组形式[连续内存空间]
3、Dict
初始化数组最小是4 size为2^n sizemask为2^n-
4. ZipList
Dict不是连续性的 每个entity都是独立的 使用了大量指针 查询快但是内存占用大,可能造成大量的内存碎片
类似于链表,节点找中间的节点性能慢,但是舍弃了指针,占用内存空间更小,节点数会做限制,总节点长度 和节点数是小端字节序表示,低位在前
连续锁更新问题
假如说现在有几个连续且节点长度都是250~253的节点,他们记录前一个节点都是用一个字节表示,因为没超过254,这时有一个超过254字节插入到队首,那么之前的头节点表示上一个节点长度的所使用的字节数就要从1变成5 导致本身超过超过254字节数,导致后面几个节点超过阈值随之更新,申请内存空间切换态消耗很大
1.5 QuickList
1.6 SkipList
查询效率非常高 可以允许32级指针 理论上说也就是 2^32 个元素
1.7 RedisObject
1.8 五种数据结构
1、String
如果小于44字节
小于44字节所采用的编码是 SDS_TYPE_8 RedisObject和SDS 加在一起刚好64字节 redis底层内存分配的算法 jemalloc
(https://blog.csdn.net/MOU_IT/article/details/118460045)
会以2^n 内存分配 而64正好是一个内存分片的大小不会产生碎片
相关文章 https://blog.csdn.net/weixin_41231928/article/details/120589257
“一个字节有8位,每一位两种状态1或者0计算机储存数据是以二进制的方式,有一位为符号位,所以最大数为01111111转化为十进制数为127。若无符号,最大数为11111111转化为十进制为255。”
2、 List
3、 Set
4、 ZSet
5、 Hash
、、
2. 网络模型
1、用户空间和内核空间
2、阻塞IO
3、 非阻塞IO
4、 IO多路复用
相关链接
select
https://blog.csdn.net/youyou1543724847/article/details/83445226
poll
epoll
5、 多路复用通知机制
6、IO多路复用web服务流程
7、 信号驱动IO 与 异步IO
7、Redis网络模型
3. Redis通信协议
1、 RESP协议
2、 模拟客户端
SocketChannel channel = SocketChannel.open();
ByteBuffer allocate = ByteBuffer.allocate(96);
channel.connect(new InetSocketAddress("localhost",6379));
allocate.put("*3\r\n$3\r\nset\r\n$4\r\nname\r\n$6\r\n张三\r\n".getBytes(StandardCharsets.UTF_8));
allocate.flip();
channel.write(allocate);
allocate.clear();
channel.read(allocate);
allocate.flip();
int limit = allocate.limit();
byte[] bytes = new byte[limit];
for (int i = 0; i < limit; i++) {
bytes[i] = allocate.get();
}
System.out.println(new String(bytes)); // 输出 [+OK]