logo免费下载网站,wordpress会影响网速吗,免费的拓客软件有哪些,赣州爆炸事故还差什么#xff1f;【按照这个为基础#xff0c;对照他的Redis路线图#xff0c;冲冲冲】 Redis的常见操作和命令#xff1a;Redis基本操作命令#xff08;图文详解#xff09;_redis 命令_进击小高的博客-CSDN博客 Redis的持久化#xff0c;一致性#xff1a;AOF【按照这个为基础对照他的Redis路线图冲冲冲】 Redis的常见操作和命令Redis基本操作命令图文详解_redis 命令_进击小高的博客-CSDN博客 Redis的持久化一致性AOF快照事务如何保证、双写一致性
个人总结全技术栈思维导图(持续更新) | feiyes blog
Redis的多线程这个啥时候多线程不理解
看完小林的面经后去问问 Redis 是 C语言写的 如果源码不编译是无法实现自动跳转的 Redis在win上编译有点麻烦我是使用的CentOS环境Clion编译 编译完就可以直接通过shell连接Redis server了 server.c 中放的是就是主类 6000多行左右是入口main()函数位置 Redis的使用
通过redis.conf 文件的如下位置 配置 Redis有多少个数据库 select 0 select 1对应就是数据库的序号16个数据库对应0-15下标 使用 help list去查看所有的List操作 解释一下数据类型例如我们使用的命令是ZADD那么Redis就知道我们操作的数据类型一定是Zset数据类型因此一定会在代码中检查 ZADD 后面的 操作对象是不是 zset数据类型 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 下图就是数据库的结构:
其中最重要的一项就是存放该数据库key-value对的715行 dict 类型的 *dict指针 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
dict 数据类型如下定义 83行有两个dictht 类型的数组就是对应存放 老HashTable 和 新扩容的HashTable 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
62hashFunction() 给出 key 求对应的哈希值
65keyCompare() 比较两个Key是否一致是否出现冲突一致的话执行后续逻辑操作 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
HashTable的数据结构
74 指向一个哈希表 --dictEntry就是哈希表中真正存放的entry的数据结构 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
哈希表中的每一个entry其数据结构
53行就是 val 指针 指向封装的RedisObject 各数据类型的对象 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
下图就是 RedisObject这个数据类型 // 解释下675、676 这些行的 4 是啥意思含义是强制要求这个变量只使用4bit
(1 byte 8 byte),算下来RedisObject占 16 byte
1type表征了这个RedisObject里面盛放的数据类型是什么是string list zset 还是啥别的
(2) encoding 表征了数值在内存中的编码方式RedisObject的对象类型取决于是用什么API
如下图使用API “set” 来创建对象那么无论对象赋值是什么都会被封装成string类型
而使用API “LPUSH” 则会被封装成list 类型 同时即使是相同的数据类型根据数据的不同值在内存里面用不同的方式去存储导致type相同但底层的编码都是不一样的
且数据值发生变化的时候Redis底层会帮我们把数据的encoding方式改变做一些优化
3refcount 表征 这个 RedisObject 被引用了多少次引用计数法的简单实现罢了作用也是垃圾回收
4ptr 是一个真正纸箱内存区域的一个指针代表我们将RedisObject存放到了哪里 》根据ptr就能知道数据到底真正存放在内存的哪里
4.1同时Redis也对 ptr做了优化 由于指针是8个byte而对于数值类型一般最长也就8byte
由于我们的value是SDS类型判断下SDS这个buf是不是小于20位因为有符号long 类型就20位若小于那么有希望将该value转化为long 类型保存
由于指针字节和数值字节长度一致因此Redis在能将SDS的内容转换为数值类型是就会执行转换让ptr存放数值而非指向SDS的地址指针
把已经存放数值的value变量强转为void*类型再赋值给ptr 如下图所示就可以减少内存使用减少一次IO 4.2Redis存放字符串的时候他的底层编码方式
若字符串SDS的buf[]内容 44字节encoding 方式是 embstr 44字节encoding 方式是 raw 为啥是44呢 【回答】CPU 通过地址总线 从内存中拿数据并不是想拿多少拿多少 而是每次CPU至少要拿64byte(这个大小就是 缓存行大小 cache line -- 这是为了缓存一致性原理的优化体现)即即使CPU需要的数据很少几个byte而已但是一次CPU与内存的交互 也会顺带把那些不需要的数据也拿上凑够64byte 我们观察 RedisObject 这个结构体它的大小是16byteCPU取指定的 RedisObject的某个结构体除了需要的16byte还得去会不相干的48byte于是Redis的优化是 想把 RedisObject的ptr指向的数据内容放到这48byte上这样就可以不用先将RedisObject对象放到L1 cache在找到ptr把ptr指向的地址中对应的数据内容加载到L1 cache了 而对于存放数据48字节的字符串应该对应的是 sdshdr8能表示的数据范围里而sdshdr8结构体中表征信息的len、alloc变量等需要4字节那么还剩下44字节因此只要SDS的中的buf[]大小少于44字节就可以将该字符串内容和RedisObject实例存放在一起占满64Byte的空间一次被CPU读到L1cache中 这种数据和RedisObject一起在分配空间上分配到一起的存放方式 就是 embstr (embedding string 嵌入式字符串) Redis数据库的各个数据类型总览 哪怕是同一种RedisObject 的 type结构底层数据类型实现也有很多不同如 int 、raw 、embstr 、quicklist 等等
而value最后都会放到 RedisObject里面进行封装 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
Redis的所有命令都会被封装到RedisCommand这个类里面 Redis 底层设计方式Redis底层 如何 管理数据
Redis通过 HashTable 来 存放数据
我们知道 Redis 维护的是一个个 key-value 的项entry而组织存放这些entry的数据结构是 HashTableHashTable是一个数组
通过对 entry 中 key 的 hash() 处理并且按照HashTable大小取模这样把key对应到为一个int 值。该 int 值作为下标HashTable[i] 中就存放 该entry 的 value 内容的地址即存放的是指向value值存放空间的指针注意HashTable中存的是地址不是把entry直接存进去了
由于hash函数会导致哈希冲突即不同的key可能会哈希到同一个值Redis 通过拉链法即在HashTable的值指向一个个 结构体结构体内容包括key值value的地址和 next指针用于指向下一个哈希到相同位置的entry条目同时每次出现哈希冲突新的entry对应的结构体以头插法的方式插入到链表头部新头插的entry可能会在最近被访问的概率大
而哈希冲突越来越频繁的时候拉链越来越长找到一个对应的entry时间也越来越长此时HashTable就会考虑扩容HashTable扩容的条件是若出现一条拉链链表的长度HashTable的长度那么HashTable扩容。
HashTable扩容的方式成倍扩容原来HashTable长度是 8 扩容后会开辟一个新的大小为8*216的空间放置扩容的HashTable。
而后将旧的HashTable中的拉链上的各个entry结构体重新执行哈希函数插入到扩容后的HashTable中。这个操作就被称为 rehash
主动渐进式将entry移动到新的HashTable中操作是不管get set 等任何访问HashTable的操作只要访问了HashTable就按顺序移动一个hash槽一个哈希槽对应所有被哈希函数处理后对应到该位置的entry内的所有内容。这么做是由于我们使用Redis就是为了加速而大批量新旧HashTable的数据转移会影响Redis正常功能因此要渐进式移动数据。
在渐进式移动HashTable时调用get()要求获得某个数据的value执行顺序 先去旧HashTable寻找找不到再去新的HashTable寻找
调用set()时直接向新的HashTable中插入这就是为啥访问get时会出现key找不到是因为get的value是新set的数据已经被插入到新的HashTable中了
Redis对于dict(就是Redis中的数据库rehash操作时若如208行所示若访问已经发现n*10个哈希槽中都没有数据有一个字段empty_visit记录发现了多少个空哈希槽此次访问就不rehash了下次再有操作再说。【这说明只有部分哈希槽冲突严重其他哈希槽都还好】 注意HashTable大小一般是2^X大小因为 如下图所示 当HashTable大小为2^X时取模就可以使用位运算加速 Redis 的 key 数据类型
Redis 在使用时key可以是 整形、浮点、字符串 、音频视频等但实际上Redis服务器端会将无论是什么数据类型的key都转换成 String对象
SDS数据类型
【SDS相较于char[] 做了哪些优化】
一减少len这个属性占的空间
3.2和3.2版本之前SDS 数据结构用 int 来描述SDS的buf[]长度
int占4个字节但是buf[]长度可能只有10个造成SDS实例空间浪费
结局方案是Redis6版本提出了SDS 新的数据类型实现sdshdr5
结构中包括char类型的 flag和 存放内容的buf[]
char占8字节前3位用于表明这是个SDS的数据类型后5位用来指示buf[]的长度 啥叫SDS数据类型因为SDS为了不同长度的buf定义了多种不同的 sdshdrX 数据类型有sdshdr5 , sdshdr8 等等 二预分配、懒回收
定义字符串 ss lalala时系统会自动 malloc出6个char大小存放
此时我们想在后面加一个字符就得重新malloc()malloc()函数调用要很长的时间因此每次出现扩容需求的时候若扩容内容1M : 倍增原来的buf数组长度若1M则满足新的buf数组的长度后在追加1M的大小
SDS的free变量(在6版本的Redis中改了个名字 叫 alloc还是一个意思一个用法)就是记录这个每次与分配后还剩余的空间free也因为buf的长度不同 优化为了 uint8_t 或者 uint16_t
三Redis 赋值buf[]时也会 以 \0结尾为了兼容C语言其实就是为了少写点函数用用C语言的函数
所以就比如上图的sdshdr8这个结构就是8bit的len8bit的alloc,8bit的char同时由于buf[]为了和C语言字符串统一也是以\0结尾因此要保存8bit的char总结下来就是4个字节
四二进制安全即使buf[]中出现 \0也没关系不会被处理为内容结束 List数据类型
List的常见用法API
1. 向list中追加元素
LPUSH alist a b c 》向名为alist的列表中从左侧加入了3个元素
因此 alist的内存摆放是c,b,a
2. 从list中取出元素
LPOP alist 》从名为alist的列表中从左侧取出元素并从列表中删除该元素
//由于我们刚存放的方式是 c,b,a 因此第一次LPOP取出的是 c
当列表alist中的元素全被取出Redis会把 alist 这个key也释放掉
[可以感受到list 是个双端队列]同一边放同一边取list就是个栈 一边放另一边取list就是个队列
而BLPOP key timeout 这种语句可以设置阻塞和超时若list中没有元素就等着阻塞阻塞超过设置的超时时间就不等待了
这种阻塞API读取都有参数类似BLPOP BRPOP
3. BRPOPPUSH source distination timeout :
目的是构建类似消息队列的数据结构当一个消息被pop出去在处理过程中宕机或错误处理那么这则消息就相当于丢失了BRPOPPUSH则选择对于list中的每个元素pop之前加入到source队列中这样就算处理失败该元素也不会消失依旧存在
list的底层设计
1. list中存放的元素 长度不定同时操作从list两边去读写 》因此会想到使用双端链表
但是双端链表需要2个指针一个指针8字节因此哪怕list存储的元素所占内存很少list也至少需要16字节来存放指针等基本信息。
这样的内存开销Redis不能接受因此Redis选择ziplist 作为底层数据结构存放list的各个元素
2. ziplist 底层编码【ziplist是紧凑的二进制数据编码类型,每次数据发生变动都需要新建存储空间数据移动复制指向新的空间三个步骤】
由于向ziplist中追加数据时由于 ziplist存放数据内容的空间是一段连续的空间因此每次追加都需要新开辟足够的空间、将就空间的数据赋值移动再吧新加入的数据append到新空间这个操作在ziplist存放的数据量打的时候很费时间因为又要开辟空间又要O(n)复制因此采用双层结构quicklist组织quickNodequickNode节点中的数据部分就是ziplist数据类型的。ziplist这种二进制编码方式也注定他只能每次修改数据都要 重新开辟一次空间因此现在多了quickNode并有专门的属性控制若quickNode节点中的ziplist长度过长在修改时就会影响效率那么就把一个QuickNode中的ziplist内容拆分成两个QuickNode中的ziplist 内容
如下图所示头结点尾结点的quicklist的quicknode节点不压缩这个优化的原因是因为经常使用的就是头尾节点因此不压缩就是好的 一次新开辟一个比较连续的内存空间存放ziplist:黄色部分是ziplist的描述信息
第一块32bits描述 绿色部分的数据部分占多少个字节
第二块32bits最后的一个元素在绿色数据部分的位置记录偏移量 -- 因为不一定全都把数据部分用满
第三块16bits在绿色数据部分存放了多少个元素
最后一块8bits 用于表明是ziplist的结尾
数据元素 放在绿色部分每个元素是一个entry类型
【压缩存放太复杂了别看了】-- 就是 数据存储不是字符而是二进制方式存储
而list的底层实现是用双端链表整合多个ziplist组成最终的list
quicklist 包含 以quicklsNode为元素的双端队列quicklsNode 中的数据部分就是 ziplist 》list 的type 是list 编码类型通过encoding Object aalist获得是quicklist Set数据类型
Set的API
1. 向aset 中添加内容 SADD aset 1 2 3 4 3 100 77 66
2. 获得set中的元素SMEMBERS aset
此时展示的aset中的数据内容是否有序和不重复其实因为 把数据中的内容 编码为intset因此库有序但无重复
当向 aset中添加其他数据类型如string后Redis的底层编码就会变为HashTable而此时再次调用 SMEMBERS aset 就会发现 元素 无序且不重复因为HashTable是无序的 intset数据类型就是一个数组如果往 intset中添加新元素如果存值的数组空间不够就得扩容空间够的话就把intset的部分数据往后移流出空间放新的插入数据i
不过intset由于都是数值因此可以很方便 二分查找定位元素 Hash 数据类型
常见hash的API
1. 定义一个hash对象ahash 加入键值对 k1,v1 k2,v2 hset ahash k1 v1 k2 v2
hash编码方式 当Hash数据类型时 ziplist编码时就会按顺序存放
当内容过大时就会采用hashtable存放key和value的内容此时就不再有序了 zset数据类型有序的无重复列表
zset常见API
1. 向 zset中添加元素 ZADD azset 100 a 200 b 50 c 》向azset中添加元素和他们的分值让zset根据分值排序
2. 由于zset中元素有序获取zset中 某个区间的元素集 : ZRANGE azset 0 3 withscores 获取从第0名到第3名的全部元素和他们的分值
zset 默认升序排列也可以通过 ZrevRANGE azset 0 3 withscores 获得降序排列
zset编码方式 当数据量小的时候使用ziplist
当数据量大的时候使用skiplist 跳表
由于链表要求有序但只有序是的链表也只能O(n)复杂度因此提一层索引层可以加速查找先确定范围之后下沉一层去查找如下图所示 真正实现会有很多层索引比较索引 -- 下沉查找 -- 比较索引 -- 下沉查找直到确定范围 下沉一层去查找每次可以减少一半的查找范围故跳表时间复杂度logN,N就是几层的索引 跳表的数据结构
zset的跳表数据编码的方式如下 dict字典类型 将分值 和 key进行存储
如1018所示当key或value值过大zset中的编码实现就是 跳表zskiplist
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
下图是zskiplist的数据结构
level记录索引的层次个数 zskiplistLevel就是 索引层 其中的span数据表明该层次的索引中两个索引间隔多少
跳表中的元素存在但分数发生改变若新的分时还能使得该元素在原来的位置直接重置值即可若位置改变先把原来的元素删去在把新的元素-分数插入进zset