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

在浴室里做的网站傻瓜式网站开发

在浴室里做的网站,傻瓜式网站开发,怎么做流量网站,网站开发技术交流Redis是基于内存的nosql#xff0c;有些场景下为了节省内存redis会用“时间”换“空间”。 ziplist就是很典型的例子。 ziplist是list键、hash键以及zset键的底层实现之一#xff08;3.0之后list键已经不直接用ziplist和linkedlist作为底层实现了#xff0c;取而代之的是qu…  Redis是基于内存的nosql有些场景下为了节省内存redis会用“时间”换“空间”。 ziplist就是很典型的例子。 ziplist是list键、hash键以及zset键的底层实现之一3.0之后list键已经不直接用ziplist和linkedlist作为底层实现了取而代之的是quicklist 这些键的常规底层实现如下 list键双向链表hash键字典dictzset键跳跃表zskiplist 但是当list键里包含的元素较少、并且每个元素要么是小整数要么是长度较小的字符串时redis将会用ziplist作为list键的底层实现。同理hash和zset在这种场景下也会使用ziplist。 既然已有底层结构可以实现list、hash、zset键为什么还要用ziplist呢 当然是为了节省内存空间 我们先来看看ziplist是如何压缩的 原理 整体布局 ziplist是由一系列特殊编码的连续内存块组成的顺序存储结构类似于数组ziplist在内存中是连续存储的但是不同于数组为了节省内存 ziplist的每个元素所占的内存大小可以不同数组中叫元素ziplist叫节点entry下文都用“节点”每个节点可以用来存储一个整数或者一个字符串。 下图是ziplist在内存中的布局 zlbytes: ziplist的长度单位: 字节)是一个32位无符号整数zltail: ziplist最后一个节点的偏移量反向遍历ziplist或者pop尾部节点的时候有用。zllen: ziplist的节点entry个数entry: 节点zlend: 值为0xFF用于标记ziplist的结尾 普通数组的遍历是根据数组里存储的数据类型 找到下一个元素的例如int类型的数组访问下一个元素时每次只需要移动一个sizeof(int)就行实际上开发者只需让指针p1就行在这里引入sizeof(int)只是为了说明区别。 上文说了ziplist的每个节点的长度是可以不一样的而我们面对不同长度的节点又不可能直接sizeof(entry)那么它是怎么访问下一个节点呢ziplist将一些必要的偏移量信息记录在了每一个节点里使之能跳到上一个节点或下一个节点。 接下来我们看看节点的布局 节点的布局(entry) 每个节点由三部分组成prevlength、encoding、data prevlengh: 记录上一个节点的长度为了方便反向遍历ziplistencoding: 当前节点的编码规则下文会详细说data: 当前节点的值可以是数字或字符串  为了节省内存根据上一个节点的长度prevlength 可以将ziplist节点分为两类 entry的前8位小于254则这8位就表示上一个节点的长度entry的前8位等于254则意味着上一个节点的长度无法用8位表示后面32位才是真实的prevlength。用254 不用255(11111111)作为分界是因为255是zlend的值它用于判断ziplist是否到达尾部。 根据当前节点存储的数据类型及长度可以将ziplist节点分为9类 其中整数节点分为6类  整数节点的encoding的长度为8位其中高2位用来区分整数节点和字符串节点高2位为11时是整数节点低6位用来区分整数节点的类型定义如下: #define ZIP_INT_16B (0xc0 | 04)//整数data,占16位2字节 #define ZIP_INT_32B (0xc0 | 14)//整数data,占32位4字节 #define ZIP_INT_64B (0xc0 | 24)//整数data,占64位8字节 #define ZIP_INT_24B (0xc0 | 34)//整数data,占24位3字节 #define ZIP_INT_8B 0xfe //整数data,占8位1字节 /* 4 bit integer immediate encoding */ //整数值1~13的节点没有dataencoding的低四位用来表示data #define ZIP_INT_IMM_MASK 0x0f #define ZIP_INT_IMM_MIN 0xf1 /* 11110001 */ #define ZIP_INT_IMM_MAX 0xfd /* 11111101 */ 值得注意的是 最后一种encoding是存储整数0~12的节点的encoding它没有额外的data部分encoding的高4位表示这个类型低4位就是它的data。这种类型的节点的encoding大小介于ZIP_INT_24B与ZIP_INT_8B之间1~13但是为了表示整数0取出低四位xxxx之后会将其-1作为实际的data值0~12。在函数zipLoadInteger中我们可以看到这种类型节点的取值方法 ...} else if (encoding ZIP_INT_IMM_MIN encoding ZIP_INT_IMM_MAX) {ret (encoding ZIP_INT_IMM_MASK)-1;} ... 字符串节点分为3类 当data小于63字节时(2^6)节点存为上图的第一种类型高2位为00低6位表示data的长度。当data小于16383字节时(2^14)节点存为上图的第二种类型高2位为01后续14位表示data的长度。当data小于4294967296字节时(2^32)节点存为上图的第二种类型高2位为10下一字节起连续32位表示data的长度。 上图可以看出 不同于整数节点encoding永远是8位字符串节点的encoding可以有8位、16位、40位三种长度 相同encoding类型的整数节点 data长度是固定的但是相同encoding类型的字符串节点data长度取决于encoding后半部分的值。 #define ZIP_STR_06B (0 6)//字符串data,最多有2^6字节(encoding后半部分的length有6位,length决定data有多少字节) #define ZIP_STR_14B (1 6)//字符串data,最多有2^14字节 #define ZIP_STR_32B (2 6)//字符串data,最多有2^32字节 上文介绍了ziplist节点entry的分类知道了节点可以细分为9种类型那么当遍历一个ziplist时指针到达某个节点时 如何判断出节点的类型从而找到data呢 已知节点的位置求data的值 根据图2 entry布局 可以看出若要算出data的偏移量得先计算出prevlength所占内存大小1字节和5字节 //根据ptr指向的entry返回这个entry的prevlensize #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \if ((ptr)[0] ZIP_BIGLEN) { \(prevlensize) 1; \} else { \(prevlensize) 5; \} \ } while(0); 接着再用ZIP_DECODE_LENGTH(ptr prevlensize, encoding, lensize, len)算出encoding所占的字节返回给lensizedata所占的字节返回给len //根据ptr指向的entry求出该entry的lenencoding里存的 data所占字节和lensizeencoding所占的字节 #define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \ZIP_ENTRY_ENCODING((ptr), (encoding)); \if ((encoding) ZIP_STR_MASK) { \if ((encoding) ZIP_STR_06B) { \(lensize) 1; \(len) (ptr)[0] 0x3f; \} else if ((encoding) ZIP_STR_14B) { \(lensize) 2; \(len) (((ptr)[0] 0x3f) 8) | (ptr)[1]; \} else if (encoding ZIP_STR_32B) { \(lensize) 5; \(len) ((ptr)[1] 24) | \((ptr)[2] 16) | \((ptr)[3] 8) | \((ptr)[4]); \} else { \assert(NULL); \} \} else { \(lensize) 1; \(len) zipIntSize(encoding); \} \ } while(0);//将ptr的encoding解析成1个字节00000000、01000000、10000000(字符串类型)和11??????(整数类型) //如果是整数类型encoding直接照抄ptr的;如果是字符串类型encoding被截断成一个字节并清零后6位 #define ZIP_ENTRY_ENCODING(ptr, encoding) do { \(encoding) (ptr[0]); \if ((encoding) ZIP_STR_MASK) (encoding) ZIP_STR_MASK; \ } while(0)//根据encoding返回数据(整数)所占字节数 unsigned int zipIntSize(unsigned char encoding) {switch(encoding) {case ZIP_INT_8B: return 1;case ZIP_INT_16B: return 2;case ZIP_INT_24B: return 3;case ZIP_INT_32B: return 4;case ZIP_INT_64B: return 8;default: return 0; /* 4 bit immediate */}assert(NULL);return 0; } 完成以上步骤之后即可算出data的位置:ptrprevlensizelensize以及data的长度len ziplist接口 上文已经阐述了ziplist的底层内存布局接下来看看一些基本的增删改查操作在ziplist中是如何执行的。 ziplistNew 创建一个ziplist O(1) /* Create a new empty ziplist. */ unsigned char *ziplistNew(void) {unsigned int bytes ZIPLIST_HEADER_SIZE1;//zlbytes4字节zltail4字节zllen2字节zlend1字节没有entry节点unsigned char *zl zmalloc(bytes);ZIPLIST_BYTES(zl) intrev32ifbe(bytes);//zlbytes赋值ZIPLIST_TAIL_OFFSET(zl) intrev32ifbe(ZIPLIST_HEADER_SIZE);//zltailZIPLIST_LENGTH(zl) 0;//zllenzl[bytes-1] ZIP_END;//zlendreturn zl; } #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2sizeof(uint16_t))//空ziplist除了zlend的大小 #define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))//zlbyte的指针的值可读可写 #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)sizeof(uint32_t))))//zltail的指针的值 #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2sizeof(uint16_t))//空ziplist除了zlend的大小 #define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)sizeof(uint32_t)*2)))//zllen的指针的值 参照着图1理解会直观些分配了一块内存并初始化zlbyteszltailzllenzlend没有entry。 ziplistFind 从ziplist里找出一个entry O(n) //返回p节点之后data与vstr(长度是vlen)相等的节点只找p节点之后每隔skip的节点 //时间复杂度 O(n) unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {int skipcnt 0;unsigned char vencoding 0;long long vll 0;while (p[0] ! ZIP_END) {unsigned int prevlensize, encoding, lensize, len;unsigned char *q;ZIP_DECODE_PREVLENSIZE(p, prevlensize);ZIP_DECODE_LENGTH(p prevlensize, encoding, lensize, len);q p prevlensize lensize;//当前节点的dataif (skipcnt 0) {/* Compare current entry with specified entry */if (ZIP_IS_STR(encoding)) {//判断当前节点是不是字符串节点if (len vlen memcmp(q, vstr, vlen) 0) {return p;}} else {/* Find out if the searched field can be encoded. Note that* we do it only the first time, once done vencoding is set* to non-zero and vll is set to the integer value. */if (vencoding 0) {//这个代码块只会执行一次,计算vstr的整数表示if (!zipTryEncoding(vstr, vlen, vll, vencoding)) {//将参数给的节点vstr当做整数节点转换将data值返回给vll节点编码返回给vencoding//进入这个代码块说明将vstr转换成整数失败vencoding不变下次判断当前节点是整数节点之后可以跳过这个节点/* If the entry cant be encoded we set it to* UCHAR_MAX so that we dont retry again the next* time. */vencoding UCHAR_MAX;//当前节点是整数节点但是vstr是字符串节点跳过不用比较了}/* Must be non-zero by now */assert(vencoding);}/* Compare current entry with specified entry, do it only* if vencoding ! UCHAR_MAX because if there is no encoding* possible for the field it cant be a valid integer. */if (vencoding ! UCHAR_MAX) {long long ll zipLoadInteger(q, encoding);//算出当前节点的dataif (ll vll) {return p;}}}/* Reset skip count */skipcnt skip;} else {/* Skip entry */skipcnt--;}/* Move to next entry */p q len;}return NULL; }//尝试将entry地址的内容转换成整数并根据这个整数算出一个合适的encoding返回给encoding参数。 //若无法转换成整数则encoding不变返回0等到下次调用zipEncodeLength时再计算一个该字符串的encoding int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {long long value;if (entrylen 32 || entrylen 0) return 0;if (string2ll((char*)entry,entrylen,value)) {/* Great, the string can be encoded. Check whats the smallest* of our encoding types that can hold this value. */if (value 0 value 12) {*encoding ZIP_INT_IMM_MINvalue;} else if (value INT8_MIN value INT8_MAX) {*encoding ZIP_INT_8B;} else if (value INT16_MIN value INT16_MAX) {*encoding ZIP_INT_16B;} else if (value INT24_MIN value INT24_MAX) {*encoding ZIP_INT_24B;} else if (value INT32_MIN value INT32_MAX) {*encoding ZIP_INT_32B;} else {*encoding ZIP_INT_64B;}*v value;return 1;}return 0; }/* Read integer encoded as encoding from p */ int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {int16_t i16;int32_t i32;int64_t i64, ret 0;if (encoding ZIP_INT_8B) {ret ((int8_t*)p)[0];} else if (encoding ZIP_INT_16B) {memcpy(i16,p,sizeof(i16));memrev16ifbe(i16);ret i16;} else if (encoding ZIP_INT_32B) {memcpy(i32,p,sizeof(i32));memrev32ifbe(i32);ret i32;} else if (encoding ZIP_INT_24B) {i32 0;memcpy(((uint8_t*)i32)1,p,sizeof(i32)-sizeof(uint8_t));memrev32ifbe(i32);ret i328;} else if (encoding ZIP_INT_64B) {memcpy(i64,p,sizeof(i64));memrev64ifbe(i64);ret i64;} else if (encoding ZIP_INT_IMM_MIN encoding ZIP_INT_IMM_MAX) {ret (encoding ZIP_INT_IMM_MASK)-1;} else {assert(NULL);}return ret; } 其他接口 ziplistInsert 往ziplist里插入一个entry 时间复杂度 平均:O(n), 最坏:O(n²)ziplistDelete 从siplist里删除一个entry 时间复杂度 平均:O(n), 最坏:O(n²) 为什么插入节点和删除节点两个接口的最坏时间复杂度会是O(n²)呢这是由于ziplist的“连锁更新”导致的连锁更新在最坏情况下需要对ziplist执行n次空间重分配操作而且每次空间重分配的最坏时间复杂度为O(n) ----《Redis设计与实现》 但是出现“连锁更新”的情况并不多见所以这里基本不会造成性能问题。 篇幅有限这里不能细说连锁更新感兴趣可以阅读《Redis设计与实现》的相关章节以及ziplist.c里的__ziplistCascadeUpdate()函数。 总结 ziplist是为节省内存空间而生的。ziplist是一个为Redis专门提供的底层数据结构之一本身可以有序也可以无序。当作为list和hash的底层实现时节点之间没有顺序当作为zset的底层实现时节点之间会按照大小顺序排列。
http://www.pierceye.com/news/25456/

相关文章:

  • 做解析会员电影的网站青岛销售系统app开发
  • 网站建设合作协议书htmlplay
  • 河北邯郸wap网站建设如何做让公众都知道的网站
  • 怀宁网站建设前端培训出来工资多少
  • 国际网站建设招标听说上海又要封了
  • 怎么盗号网站怎么做免费建站微信
  • 网站设置会员中山制作企业网站
  • wordpress 在线教育模板seo网站基础建设
  • 网站开发的学习博客网站建设设计论文总结
  • 姓氏网站建设的意见和建议交易网站怎么做
  • 网站开发项目教程笔记哪个网站可以学做包包
  • 检测网站死链网站设计师的工作内容
  • 南昌哪里可以做电商网站做网站编程
  • 在线设计网站哪个好wordpress登录选项
  • 上海市嘉定区建设银行网站网站空间查询
  • 上海百度公司地址谷歌优化技巧
  • 运城公司做网站旅游网站模板下载
  • 恶意刷网站北京工商注册代理
  • 简述营销网站建设包括哪些内容莱阳seo外包
  • 东营网站制作团队如何提高网站访问速度
  • 中山网站模板seo流量增加软件
  • 网站升级及政务新媒体建设方案学校网站建设策划
  • 套模板做网站教程印刷网络商城网站建设
  • 重庆制作网站培训机构wordpress 使用教程
  • 关于产品网站建设的问题qq代挂网站建设
  • 银河盛世网站建设抖音电商官网
  • 做网站在哪儿买空间成都品牌推广
  • 自己制作网站的方法是舟山建设网站
  • 广州网站备案号成全视频免费观看在线看第6季高清版下载
  • 外包公司做网站多少访问网页