当前位置: 首页 > news >正文

网站开发与设计岗位职责建设医院官方网站

网站开发与设计岗位职责,建设医院官方网站,wordpress网站管理,购物网站服务中心Redis设计与实现笔记 - 数据结构篇 相信在我们日常使用中#xff0c;会经常跟 Redis 打交道。数据结构 String、Hash、List、Set 和 ZSet 都是常用的数据类型。对于使用场景#xff0c;我们可以滔滔不绝地说很多#xff0c;但是我们从来就没有关心过它们的底层实现#xf…Redis设计与实现笔记 - 数据结构篇 相信在我们日常使用中会经常跟 Redis 打交道。数据结构 String、Hash、List、Set 和 ZSet 都是常用的数据类型。对于使用场景我们可以滔滔不绝地说很多但是我们从来就没有关心过它们的底层实现到底它们的数据是怎么存储的代码是怎么实现的使用上有什么值得注意的地方。带着这些疑问我去查看了相关的书籍对于实现有了大致的认识。希望你看完后也有所收获。 先统一名词我们常用的 String、Hash、List、Set 和 ZSet 叫做对象Object。它们由如 SDS、LinkList、Skiplist 等基础数据结构组成。 数据结构 简单动态字符串 - SDS struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* 已使用的长度 */uint64_t alloc; /* 分配的长度 不包含头部和空终止符号 */unsigned char flags; /* 3位最低有效位表示类型, 其余5个比特位未被使用 */char buf[]; }; 常数复杂度获取字符串长度。C 语言中的传统字符串类型需要遍历整个字符串才能获取字符串长度时间复杂度为 O(n)而 SDS 可以直接获取字符串长度时间复杂度为 O(1)。 杜绝缓冲区溢出。SDS 在字符串末尾预留了额外的空间当字符串长度增加时可以直接使用预留的空间避免了缓冲区溢出的问题。 减少修改字符串长度时带来的内存重分配次数。SDS 采用了空间预分配和惰性空间释放等策略可以减少修改字符串长度时带来的内存重分配次数提高性能。 可含有空字符等特殊字符 主要用于字符串对象的底层实现 链表 /* 双端链表节点 */ typedef struct listNode {/* 指向前驱节点的指针 */ struct listNode *prev;/* 指向后继节点的指针 */ struct listNode *next;/* void * 指针指向具体的元素节点可以是任意类型 */ void *value; } listNode;/* 双端链表迭代器 */ typedef struct listIter {/* 指向遍历的下一个节点的指针 */listNode *next;/* 遍历的方向从表头遍历还是从表尾遍历 */int direction; } listIter;/* 双端链表* 有记录头尾两节点支持从链表头部或者尾部进行遍历是早期列表键 PUSH/POP 实现高效的关键* 每个链表节点有记录前驱节点和后继节点的指针可以使得列表键支持往后或者往前进行遍历* 有额外用 len 存储链表长度O(1) 的时间复杂度获取节点个数是 LLEN 命令高效的关键 */ typedef struct list {/* 指向链表头节点的指针支持从表头开始遍历 */listNode *head;/* 指向链表尾节点的指针支持从表尾开始遍历 */listNode *tail;/* 各种类型的链表可以定义自己的复制函数 / 释放函数 / 比较函数 */void *(*dup)(void *ptr);void (*free)(void *ptr);int (*match)(void *ptr, void *key);/* 链表长度即链表节点数量O(1) 时间复杂度获取 */unsigned long len; } list; 常数复杂度获取链表长度 双端链表实现 多态实现,各种类型的链表可以自己定义各自的复制函数 / 释放函数 / 比较函数 主要用于列表对象的底层实现 字典 typedef struct dictEntry {/* void * 类型的 key可以指向任意类型的键 */void *key;/* 联合体 v 中包含了指向实际值的指针 *val、无符号的 64 位整数、有符号的 64 位整数以及 double 双精度浮点数。* 这是一种节省内存的方式因为当值为整数或者双精度浮点数时由于它们本身就是 64 位的void *val 指针也是占用 64 位64 操作系统下* 所以它们可以直接存在键值对的结构体中避免再使用一个指针从而节省内存开销8 个字节* 当然也可以是 void *存储任何类型的数据最早 redis1.0 版本就只是 void* */union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next; /* Next entry in the same hash bucket. *//* 同一个 hash 桶中的下一个条目.* 通过形成一个链表解决桶内的哈希冲突. */void *metadata[]; /* An arbitrary number of bytes (starting at a* pointer-aligned address) of size as returned* by dictTypes dictEntryMetadataBytes(). *//* 一块任意长度的数据 (按 void* 的大小对齐),* 具体长度由 dictType 中的* dictEntryMetadataBytes() 返回. */ } dictEntry;typedef struct dict dict;/* 字典类型因为我们会将字典用在各个地方例如键空间、过期字典等等等只要是想用字典哈希表的场景都可以用* 这样的话每种类型的字典它对应的 key / value 肯定类型是不一致的这就需要有一些自定义的方法例如键值对复制、析构等 */ typedef struct dictType {/* 字典里哈希表的哈希算法目前使用的是基于 DJB 实现的字符串哈希算法* 比较出名的有 siphashredis 4.0 中引进了它。3.0 之前使用的是 DJBX33A3.0 - 4.0 使用的是 MurmurHash2 */uint64_t (*hashFunction)(const void *key);/* 键拷贝 */void *(*keyDup)(dict *d, const void *key);/* 值拷贝 */void *(*valDup)(dict *d, const void *obj);/* 键比较 */int (*keyCompare)(dict *d, const void *key1, const void *key2);/* 键析构 */void (*keyDestructor)(dict *d, void *key);/* 值析构 */void (*valDestructor)(dict *d, void *obj);/* 字典里的哈希表是否允许扩容 */int (*expandAllowed)(size_t moreMem, double usedRatio);/* Allow a dictEntry to carry extra caller-defined metadata. The* extra memory is initialized to 0 when a dictEntry is allocated. *//* 允许调用者向条目 (dictEntry) 中添加额外的元信息.* 这段额外信息的内存会在条目分配时被零初始化. */size_t (*dictEntryMetadataBytes)(dict *d); } dictType;/* 通过指数计算哈希表的大小见下面 exp哈希表大小目前是严格的 2 的幂 */ #define DICTHT_SIZE(exp) ((exp) -1 ? 0 : (unsigned long)1(exp)) /* 计算掩码哈希表的长度 - 1用于计算键在哈希表中的位置下标索引 */ #define DICTHT_SIZE_MASK(exp) ((exp) -1 ? 0 : (DICTHT_SIZE(exp))-1)/* 7.0 版本之前的字典结构 typedef struct dictht {dictEntry **table; // 8 bytesunsigned long size; // 8 bytesunsigned long sizemask; // 8 bytesunsigned long used; // 8 bytes } dictht;typedef struct dict {dictType *type; // 8 bytesvoid *privdata; // 8 bytesdictht ht[2]; // 32 bytes * 2 64 byteslong rehashidx; // 8 bytesint16_t pauserehash; // 2 bytes } dict;** 做的优化大概是这样的* 1. 从字典结构里删除 privdata 这个扩展其实一直是个 dead code会影响很多行社区里的做法都是想尽量减少 diff 变更避免说破坏 git blame log* 2. 将 dictht 字典哈希表结构融合进 dict 字典结构里相关元数据直接放到了 dict 中* 3. 去掉 sizemark 字段这个值可以通过 size - 1 计算得到这样就可以少 8 字节* 4. 将 size 字段转变为 size_exp就是 2 的 n 次方指数因为 size 目前是严格都是 2 的幂size_exp 存储指数而不是具体数值size 内存占用从 8 字节降到了 1 字节** 内存方面* 默认情况下通过 sizeof 我们是可以看到新 dict 是 56 个字节* dict一个指针 两个指针 两个 unsigned long 一个 long 一个 int16_t 两个 char总共实际上是 52 个字节但是因为 jemalloc 内存分配机制实际会分配 56 个字节* 而实际上因为对齐最后的 int16_t pauserehash 和 char ht_size_exp[2] 加起来是占用 8 个字节代码注释也有说将小变量放到最后来获得最小的填充。*/struct dict {/* 字典类型8 bytes */dictType *type;/* 字典中使用了两个哈希表,* (看看那些以 ht_ 为前缀的成员, 它们都是一个长度为 2 的数组)** 我们可以将它们视为* struct{* ht_table[2];* ht_used[2];* ht_size_exp[2];* } hash_table[2];* 为了优化字典的内存结构,* 减少对齐产生的空洞,* 我们将这些数据分散于整个结构体中.** 平时只使用下标为 0 的哈希表.* 当需要进行 rehash 时 (rehashidx ! -1),* 下标为 1 的一组数据会作为一组新的哈希表,* 渐进地进行 rehash 避免一次性 rehash 造成长时间的阻塞.* 当 rehash 完成时, 将新的哈希表置入下标为 0 的组别中,* 同时将 rehashidx 置为 -1.*/dictEntry **ht_table[2];/* 哈希表存储的键数量它与哈希表的大小 size 的比值就是 load factor 负载因子* 值越大说明哈希碰撞的可能性也越大字典的平均查找效率也越低* 理论上负载因子 1 的时候字典能保持平均 O(1) 的时间复杂度查询* 当负载因子等于哈希表大小的时候说明哈希表退化成链表了此时查询的时间复杂度退化为 O(N)* redis 会监控字典的负载因子在负载因子变大的时候会对哈希表进行扩容后面会提到的渐进式 rehash */unsigned long ht_used[2];long rehashidx; /* rehashing not in progress if rehashidx -1 *//* rehash 的进度.* 如果此变量值为 -1, 则当前未进行 rehash. *//* Keep small vars at end for optimal (minimal) struct padding *//* 将小尺寸的变量置于结构体的尾部, 减少对齐产生的额外空间开销. */int16_t pauserehash; /* If 0 rehashing is paused (0 indicates coding error) *//* 如果此变量值 0 表示 rehash 暂停* (0 表示编写的代码出错了). *//* 存储哈希表大小的指数表示通过这个可以直接计算出哈希表的大小例如 exp 10, size 2 ** 10* 能避免说直接存储 size 的实际值以前 8 字节存储的数值现在变成 1 字节进行存储 */signed char ht_size_exp[2]; /* exponent of size. (size 1exp) *//* 哈希表大小的指数表示.* (以 2 为底, 大小 1 指数) */ }; 结构体有点长,简单展示就是如下 dictEntry 存的是一个链表,因为redis解决hash冲突的方式是使用链地址法,其他解决方法可参考  解决哈希冲突的常用方法分析 - 腾讯云 这里注意一下ht这个字段,正常来说一个ht[1]是不会使用的,只有在rehash过程中才会有值 rehash 字典的负载因子load factor超过一定阈值时就会启动rehash, 在过程中对于字典的增删改查都会先查一遍ht[0],然后把值迁移到ht[1],等到ht[0]迁移完毕就会释放ht[0],将ht[1]设置成ht[0] 主要用于哈希对象和数据库的底层实现,对,没错,整个数据库也是一个大哈希实现 跳表 /* ZSETs use a specialized version of Skiplists */ typedef struct zskiplistNode {sds ele;double score;struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span;} level[]; } zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level; } zskiplist; zskiplist 使用双端链表实现 在遍历操作时只需要用到 forward 前行指针,查找过程 如果下一个节点比目标节点大,则移动到下一个节点 否则移动到下一层 重复以上步骤,直到找到目标值 跳表与字典结合作为有序集合的底层实现 整数集合 /* 整数集合 * 记录不包含重复元素的各个整数(由小到大的顺序) * 底层数组默认是 int16_t 类型, 可能随着新增元素的大小升级至 int32_t 或 int64_t 类型*/ typedef struct intset {/* 编码, 记录整数集合底层数组(contents)的类型*/uint32_t encoding;/* 记录整数集合包含的元素个数 */uint32_t length;/* 整数集合的底层实现, 虽声明为 int8_t 类型,但真正的类型取决于 encoding */int8_t contents[]; } intset; 支持类型 int16_t, int32_t, int64_t 在插入一个不同当前编码的值就会触发升级,遍历所有value转换类型,且不支持降级 原本 1, 2, 3 位 0-15位 16-31位 32-48位 48-127位 元素 1 2 3 新分配空间 插入 65535 位 0-31位 32-63位 64-95位 96-127位 元素 1 2 3 65535 用于当value都是整数,并且个数不超过512的集合底层实现 压缩列表 ziplist 压缩列表是由一系列字节数组表示的每个字节数组可以表示一个节点的信息包括节点的类型、长度和值等信息。 压缩列表的布局如下 zlbytes zltail zllen entry entry ... entry zlend 属性 类型 长度 用途 zlbytes uint32_t 4字节 记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配 或者计算zlend 的位置时使用 zltail uint32_t 4字节 或者计算zlend 的位置时使用 记录压缩列表表尾节点西商压缩列表的起始地址有多少字节:通过这个 偏移量程序无须遮历整个压缩列表就可以确定表尾节点的地址 zllen uint16_t 2字节 记录了压缩列表包含的节点数量:当这个属性的值小于(65535)时这个属性的值就是压缩列表包含节点的数量:当这个值等于 UINT16_MAx 时节点的真实数量需要追历整个压缩列表才能计算得出 entry 列表节点 不定 压缩列表包含的各个节点节点的长度由节点保存的内容决定 zlend uint8_t 1字节 特殊值O x FF十进制255用于标记压缩列表的末端。 entry的布局如下 属性 用途 previous_entry_iength 前置节点长度记录了前一个节点的字节数它的长度可以是 1 个字节或 5 个字节具体占用的字节数取决于前一个节点的字节数,用于支持列表的反向遍历 encoding 节点类型记录了该节点存储的数据类型它的长度为 1 个字节, 字符串节点的类型为 0xc0二进制表示为 11000000 整数节点的类型为 0x00二进制表示为 00000000。 content 节点值记录了该节点存储的数据它的长度为节点长度所记录的字节数。如果该节点是字符串节点则节点值存储的是字符串的值如果该节点是整数节点则节点值存储的是整数的值。 连锁更新 由于previous_entry_length的长度是1或5,取决于前一个节点的长度,如果有个列表,每个节点的长度都是250-253之间,那么当第一个节点增加了长度,后续每一个节点需要增加长度,作者称这种现象为连锁更新,需要o(n)复杂度去更新每一个节点 为了解决这个问题, 后续在Redis7.0全面使用listpack代替了ziplist, 详细参与: Redis7.0代码分析底层数据结构listpack实现原理 - 掘金 用于列表和哈希的底层实现 后续…. Redis3.2之后引入quicklist 引用 redis7源码中文注释 Redis设计与实现
http://www.yingshimen.cn/news/28241/

相关文章:

  • 个人网站可以做商业吗深圳网站建公司
  • 青岛做网站优化无锡百度网站推广渠道
  • 两人合伙做网站但不准备开公司移动网站建设书
  • 哈尔滨建设局网站兰州专业做网站的公司
  • wordpress站点被删做外卖有哪些网站
  • phpcmsv9蓝色简洁下载网站模板品牌网站建设哪里好
  • 长春自助建站系统佛山专业网站设计公司
  • 律师微网站制作800元做小程序网站
  • 网站建设的基本流程可分为中国景观设计公司十强
  • 泉州市做网站优化游戏网页设计作品
  • 网站源码爬取工具网站网页的区别与联系
  • 江苏通力建设官方网站国内旅游网站排名
  • 电子商务网站 功能北京做兼职从哪个网站
  • 广州做网站建设哪家公司好一台电脑赚钱的门路
  • 门户网站的特点和优势怎么找精准客户资源
  • 市辖区郑州网站建设网站开发加盟
  • 白云手机网站建设价格好网站的标准
  • 杭州网站搭建中国空间雷达卫星
  • 现在最流行的网站开发工具wordpress自动播放视频
  • 网页设计策划书ppt温州谷歌优化公司
  • 珠海网站建设贵公司Iis 建网站为什么说没有该用户
  • 南昌做微网站wordpress 调用文章id
  • 用织梦做的网站南昌网站建设公司渠道
  • 怎么注册网站 个人企业公司建站平台
  • 系统花钱做任务的小说魅网站wordpress 页头
  • 做资讯网站需要什么资质做一个电商网站要多少钱
  • 医院网站建设熊掌号帮助企业做网站的销售
  • 诸暨公司制作网站需要哪些商业网站建设预估收益
  • 视频素材网站建设网站收录是什么意思?
  • 租房子58同城seo优化顾问服务