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

网站设计软件microsoft2013上海网站建设上海网站制作

网站设计软件microsoft2013,上海网站建设上海网站制作,电商运营怎么自学,文字生成图片在线使用简介#xff1a;NoSQL泛指非关系型数据库#xff0c;随着web2.0互联网的诞生#xff0c;传统的关系型数据库很难对付web2.0大数据时代#xff01;尤其是超大规模的高并发的社区#xff0c;暴露出来很多难以克服的问题#xff0c;NoSQL在当今大数据环境下发展的十分迅速NoSQL泛指非关系型数据库随着web2.0互联网的诞生传统的关系型数据库很难对付web2.0大数据时代尤其是超大规模的高并发的社区暴露出来很多难以克服的问题NoSQL在当今大数据环境下发展的十分迅速Redis是发展最快的。 作者 | 一洺 来源 | 阿里技术公众号 一 什么是NoSQL Nosql not only sql不仅仅是SQL 关系型数据库列行同一个表下数据的结构是一样的。 非关系型数据库数据存储没有固定的格式并且可以进行横向扩展。 NoSQL泛指非关系型数据库随着web2.0互联网的诞生传统的关系型数据库很难对付web2.0大数据时代尤其是超大规模的高并发的社区暴露出来很多难以克服的问题NoSQL在当今大数据环境下发展的十分迅速Redis是发展最快的。 传统RDBMS和NoSQL RDBMS- 组织化结构- 固定SQL- 数据和关系都存在单独的表中行列- DML数据操作语言、DDL数据定义语言等- 严格的一致性ACID): 原子性、一致性、隔离性、持久性- 基础的事务 NoSQL- 不仅仅是数据- 没有固定查询语言- 键值对存储redis、列存储HBase、文档存储MongoDB、图形数据库不是存图形放的是关系Neo4j- 最终一致性BASE基本可用、软状态/柔性事务、最终一致性 二 redis是什么 Redis Remote Dictionary Server即远程字典服务。 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库并提供多种语言的API。 与memcached一样为了保证效率数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件并且在此基础上实现了master-slave(主从)同步。 三 redis五大基本类型 Redis是一个开源内存存储的数据结构服务器可用作数据库高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合位图hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能同时通过Redis Sentinel提供高可用通过Redis Cluster提供自动分区。 由于redis类型大家很熟悉且网上命令使用介绍很多下面重点介绍五大基本类型的底层数据结构与应用场景以便当开发时可以熟练使用redis。 1 String字符串 1.String类型是redis的最基础的数据结构也是最经常使用到的类型。而且其他的四种类型多多少少都是在字符串类型的基础上构建的所以String类型是redis的基础。 2.String 类型的值最大能存储 512MB这里的String类型可以是简单字符串、复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串 应用场景 1、缓存功能String字符串是最常用的数据类型不仅仅是redis各个语言都是最基本类型因此利用redis作为缓存配合其它数据库作为存储层利用redis支持高并发的特点可以大大加快系统的读写速度、以及降低后端数据库的压力。 2、计数器许多系统都会使用redis作为系统的实时计数器可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。 3、统计多单位的数量eguidgongming count0 根据不同的uid更新count数量。 4、共享用户session用户重新刷新一次界面可能需要访问一下数据进行重新登录或者访问页面缓存cookie这两种方式做有一定弊端1每次都重新登录效率低下 2cookie保存在客户端有安全隐患。这时可以利用redis将用户的session集中管理在这种模式只需要保证redis的高可用每次用户session的更新和获取都可以快速完成。大大提高效率。 2 List列表 1.list类型是用来存储多个有序的字符串的列表当中的每一个字符看做一个元素 2.一个列表当中可以存储有一个或者多个元素redis的list支持存储2^32次方-1个元素。 3.redis可以从列表的两端进行插入pubsh和弹出pop元素支持读取指定范围的元素集或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构它可以充当队列或者栈的角色。 4.redis列表是链表型的数据结构所以它的元素是有序的而且列表内的元素是可以重复的。意味着它可以根据链表的下标获取指定的元素和某个范围内的元素集。 应用场景 1、消息队列reids的链表结构可以轻松实现阻塞队列可以使用左进右出的命令组成来完成队列的设计。比如数据的生产者可以通过Lpush命令从左边插入数据多个数据消费者可以使用BRpop命令阻塞的“抢”列表尾部的数据。 2、文章列表或者数据分页展示的应用。比如我们常用的博客网站的文章列表当用户量越来越多时而且每一个用户都有自己的文章列表而且当文章多时都需要分页展示这时可以考虑使用redis的列表列表不但有序同时还支持按照范围内获取元素可以完美解决分页查询功能。大大提高查询效率。 3 Set集合 1.redis集合set类型和list列表类型类似都可以用来存储多个字符串元素的集合。 2.但是和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的不存在元素下标。 3.redis的set类型是使用哈希表构造的因此复杂度是O(1)它支持集合内的增删改查并且支持多个集合间的交集、并集、差集操作。可以利用这些集合操作解决程序开发过程当中很多数据集合间的问题。 应用场景 1、标签比如我们博客网站常常使用到的兴趣标签把一个个有着相同爱好关注类似内容的用户利用一个标签把他们进行归并。 2、共同好友功能共同喜好或者可以引申到二度好友之类的扩展应用。 3、统计网站的独立IP。利用set集合当中元素不唯一性可以快速实时统计访问网站的独立IP。 数据结构 set的底层结构相对复杂写使用了intset和hashtable两种数据结构存储intset可以理解为数组。 4 sorted set有序集合 redis有序集合也是集合类型的一部分所以它保留了集合中元素不能重复的特性但是不同的是有序集合给每个元素多设置了一个分数。 redis有序集合也是集合类型的一部分所以它保留了集合中元素不能重复的特性但是不同的是 有序集合给每个元素多设置了一个分数利用该分数作为排序的依据。 应用场景 1、 排行榜有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜榜单维护可能是多方面按照时间、按照播放量、按照获得的赞数等。 2、用Sorted Sets来做带权重的队列比如普通消息的score为1重要消息的score为2然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。 5 hash哈希 Redis hash数据结构 是一个键值对key-value集合,它是一个 string 类型的 field 和 value 的映射表 redis本身就是一个key-value型数据库因此hash数据结构相当于在value中又套了一层key-value型数据。 所以redis中hash数据结构特别适合存储关系型对象 应用场景 1、由于hash数据类型的key-value的特性用来存储关系型数据库中表记录是redis中哈希类型最常用的场景。一条记录作为一个key-value把每列属性值对应成field-value存储在哈希表当中然后通过key值来区分表当中的主键。 2、经常被用来存储用户相关信息。优化用户信息的获取不需要重复从数据库当中读取提高系统性能。 四 五大基本类型底层数据存储结构 在学习基本类型底层数据存储结构前首先看下redis整体的存储结构。 redis内部整体的存储结构是一个大的hashmap内部是数组实现的hashkey冲突通过挂链表去实现每个dictEntry为一个key/value对象value为定义的redisObject。 结构图如下 dictEntry是存储key-value的地方再让我们看一下dictEntry结构体 /** 字典*/ typedef struct dictEntry {// 键void *key;// 值union {// 指向具体redisObjectvoid *val;// uint64_t u64;int64_t s64;} v;// 指向下个哈希表节点形成链表struct dictEntry *next; } dictEntry; 1 redisObject 我们接着再往下看redisObject究竟是什么结构的 /** Redis 对象*/ typedef struct redisObject {// 类型 4bitsunsigned type:4;// 编码方式 4bitsunsigned encoding:4;// LRU 时间相对于 server.lruclock 24bitsunsigned lru:22;// 引用计数 Redis里面的数据可以通过引用计数进行共享 32bitsint refcount;// 指向对象的值 64-bitvoid *ptr; } robj; *ptr指向具体的数据结构的地址type表示该对象的类型即String,List,Hash,Set,Zset中的一个但为了提高存储效率与程序执行效率每种对象的底层数据结构实现都可能不止一种encoding 表示对象底层所使用的编码。 redis对象底层的八种数据结构 REDIS_ENCODING_INTlong 类型的整数REDIS_ENCODING_EMBSTR embstr 编码的简单动态字符串REDIS_ENCODING_RAW 简单动态字符串REDIS_ENCODING_HT 字典REDIS_ENCODING_LINKEDLIST 双端链表REDIS_ENCODING_ZIPLIST 压缩列表REDIS_ENCODING_INTSET 整数集合REDIS_ENCODING_SKIPLIST 跳跃表和字典 好了通过redisObject就可以具体指向redis数据类型了总结一下每种数据类型都使用了哪些数据结构如下图所示 前期准备知识已准备完毕下面分每种基本类型来讲。 2 String数据结构 String类型的转换顺序 当保存的值为整数且值的大小不超过long的范围使用整数存储当字符串长度不超过44字节时使用EMBSTR 编码 它只分配一次内存空间redisObject和sds是连续的内存查询效率会快很多也正是因为redisObject和sds是连续在一起伴随了一些缺点当字符串增加的时候它长度会增加这个时候又需要重新分配内存导致的结果就是整个redisObject和sds都需要重新分配空间这样是会影响性能的所以redis用embstr实现一次分配而后,只允许读如果修改数据那么它就会转成raw编码不再用embstr编码了。 大于44字符时使用raw编码 SDS embstr和raw都为sds编码看一下sds的结构体 /* 针对不同长度整形做了相应的数据结构* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[]; };struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[]; };struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[]; };struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[]; };struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[]; }; 由于redis底层使用c语言实现可能会有疑问为什么不用c语言的字符串呢而是用sds结构体。 1) 低复杂度获取字符串长度由于len存在可以直接查出字符串长度复杂度O(1)如果用c语言字符串查询字符串长度需要遍历整个字符串复杂度为O(n) 2) 避免缓冲区溢出进行两个字符串拼接c语言可使用strcat函数但如果没有足够的内存空间。就会造成缓冲区溢出而用sds在进行合并时会先用len检查内存空间是否满足需求如果不满足进行空间扩展不会造成缓冲区溢出 3减少修改字符串的内存重新分配次数c语言字符串不记录字符串长度如果要修改字符串要重新分配内存如果不进行重新分配会造成内存缓冲区泄露 redis sds实现了空间预分配和惰性空间释放两种策略 空间预分配 1如果sds修改后sds长度len的值将于1mb那么会分配与len相同大小的未使用空间此时len与free值相同。例如修改之后字符串长度为100字节那么会给分配100字节的未使用空间。最终sds空间实际为 100 100 1(保存空字符\0) 2如果大于等于1mb每次给分配1mb未使用空间 惰性空间释放对字符串进行缩短操作时程序不立即使用内存重新分配来回收缩短后多余的字节而是使用 free 属性将这些字节的数量记录下来等待后续使用sds也提供api我们可以手动触发字符串缩短 4二进制安全因为C字符串以空字符作为字符串结束的标识而对于一些二进制文件如图片等内容可能包括空字符串因此C字符串无法正确存取而所有 SDS 的API 都是以处理二进制的方式来处理 buf 里面的元素并且 SDS 不是以空字符串来判断是否结束而是以 len 属性表示的长度来判断字符串是否结束 5遵从每个字符串都是以空字符串结尾的惯例这样可以重用 C 语言库string.h 中的一部分函数。 学习完sds我们回到上面讲到的为什么小于44字节用embstr编码呢 再看一下rejectObject和sds定义的结构短字符串的embstr用最小的sdshdr8 typedef struct redisObject {// 类型 4bitsunsigned type:4;// 编码方式 4bitsunsigned encoding:4;// LRU 时间相对于 server.lruclock 24bitsunsigned lru:22;// 引用计数 Redis里面的数据可以通过引用计数进行共享 32bitsint refcount;// 指向对象的值 64-bitvoid *ptr; } robj; struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[]; }; redisObject占用空间 4 4 24 32 64 128bits 16字节 sdshdr8占用空间 1uint8_t 1uint8_t 1 unsigned char 1buf[]中结尾的\0字符 4字节 初始最小分配为64字节所以只分配一次空间的embstr最大为 64 - 16- 4 44字节 3 List存储结构 Redis3.2之前的底层实现方式压缩列表ziplist 或者 双向循环链表linkedlist 当list存储的数据量比较少且同时满足下面两个条件时list就使用ziplist存储数据 list中保存的每个元素的长度小于 64 字节列表中数据个数少于512个 Redis3.2及之后的底层实现方式quicklist quicklist是一个双向链表而且是一个基于ziplist的双向链表quicklist的每个节点都是一个ziplist结合了双向链表和ziplist的优点。 ziplist ziplist是一种压缩链表它的好处是更能节省内存空间因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大每个元素也不大的时候就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性插入的复杂度是O(N)即每次插入都会重新进行realloc。如下图所示redisObject对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次它们在内存中是一块连续的区域。 ziplist结构如下 1、zlbytes:用于记录整个压缩列表占用的内存字节数   2、zltail记录要列表尾节点距离压缩列表的起始地址有多少字节   3、zllen记录了压缩列表包含的节点数量。 4、entryX要说列表包含的各个节点 5、zlend用于标记压缩列表的末端 为什么数据量大时不用ziplist 因为ziplist是一段连续的内存插入的时间复杂化度为O(n)而且每当插入新的元素需要realloc做内存扩展而且如果超出ziplist内存大小还会做重新分配的内存空间并将内容复制到新的地址。如果数量大的话重新分配内存和拷贝内存会消耗大量时间。所以不适合大型字符串也不适合存储量多的元素。 快速列表quickList 快速列表是ziplist和linkedlist的混合体是将linkedlist按段切分每一段用ziplist来紧凑存储多个ziplist之间使用双向指针链接。 为什么不直接使用linkedlist linkedlist的附加空间相对太高prev和next指针就要占去16个字节而且每一个结点都是单独分配会加剧内存的碎片化影响内存管理效率。 quicklist结构 typedef struct quicklist {// 指向quicklist的头部quicklistNode *head;// 指向quicklist的尾部quicklistNode *tail;unsigned long count;unsigned int len;// ziplist大小限定由list-max-ziplist-size给定int fill : 16;// 节点压缩深度设置由list-compress-depth给定unsigned int compress : 16; } quicklist;typedef struct quicklistNode {// 指向上一个ziplist节点struct quicklistNode *prev;// 指向下一个ziplist节点struct quicklistNode *next;// 数据指针如果没有被压缩就指向ziplist结构反之指向quicklistLZF结构unsigned char *zl;// 表示指向ziplist结构的总长度(内存占用长度)unsigned int sz;// ziplist数量unsigned int count : 16; /* count of items in ziplist */unsigned int encoding : 2; /* RAW1 or LZF2 */// 预留字段存放数据的方式1--NONE2--ziplistunsigned int container : 2; /* NONE1 or ZIPLIST2 */// 解压标记当查看一个被压缩的数据时需要暂时解压标记此参数为1之后再重新进行压缩unsigned int recompress : 1; /* was this node previous compressed? */unsigned int attempted_compress : 1; /* node cant compress; too small */// 扩展字段unsigned int extra : 10; /* more bits to steal for future usage */ } quicklistNode;typedef struct quicklistLZF {// LZF压缩后占用的字节数unsigned int sz; /* LZF size in bytes*/// 柔性数组存放压缩后的ziplist字节数组char compressed[]; } quicklistLZF; 结构图如下 ziplist的长度 quicklist内部默认单个ziplist长度为8k字节超出了这个字节数就会新起一个ziplist。关于长度可以使用list-max-ziplist-size决定。 压缩深度 我们上面说到了quicklist下是用多个ziplist组成的同时为了进一步节约空间Redis还会对ziplist进行压缩存储使用LZF算法压缩可以选择压缩深度。quicklist默认的压缩深度是0也就是不压缩。压缩的实际深度由配置参数list-compress-depth决定。为了支持快速push/pop操作quicklist 的首尾两个 ziplist 不压缩此时深度就是 1。如果深度为 2就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。 4 Hash类型 当Hash中数据项比较少的情况下Hash底层才用压缩列表ziplist进行存储数据随着数据的增加底层的ziplist就可能会转成dict具体配置如下 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 在List中已经介绍了ziplist下面来介绍下dict。 看下数据结构 typedef struct dict {dictType *type;void *privdata;dictht ht[2];long rehashidx; /* rehashing not in progress if rehashidx -1 */int iterators; /* number of iterators currently running */ } dict;typedef struct dictht {//指针数组这个hash的桶dictEntry **table;//元素个数unsigned long size;unsigned long sizemask;unsigned long used; } dictht;dictEntry大家应该熟悉在上面有讲使用来真正存储key-value的地方 typedef struct dictEntry {// 键void *key;// 值union {// 指向具体redisObjectvoid *val;// uint64_t u64;int64_t s64;} v;// 指向下个哈希表节点形成链表struct dictEntry *next; } dictEntry; 我们可以看到每个dict中都有两个hashtable 结构图如下 虽然dict结构有两个hashtable但是通常情况下只有一个hashtable是有值的。但是在dict扩容缩容的时候需要分配新的hashtable然后进行渐近式搬迁这时候两个hashtable存储的旧的hashtable和新的hashtable。搬迁结束后旧hashtable删除新的取而代之。 下面让我们学习下rehash全貌。 5 渐进式rehash 所谓渐进式rehash是指我们的大字典的扩容是比较消耗时间的需要重新申请新的数组然后将旧字典所有链表的元素重新挂接到新的数组下面是一个O(n)的操作。但是因为我们的redis是单线程的无法承受这样的耗时过程所以采用了渐进式rehash小步搬迁虽然慢一点但是可以搬迁完毕。 扩容条件 我们的扩容一般会在Hash表中的元素个数等于第一维数组的长度的时候就会开始扩容。扩容的大小是原数组的两倍。不过在redis在做bgsaveRDB持久化操作的过程为了减少内存页的过多分离Copy On Writeredis不会去扩容。但是如果hash表的元素个数已经到达了第一维数组长度的5倍的时候就会强制扩容不管你是否在持久化。 不扩容主要是为了尽可能减少内存页过多分离系统需要更多的开销去回收内存。 缩容条件 当我们的hash表元素逐渐删除的越来越少的时候。redis于是就会对hash表进行缩容来减少第一维数组长度的空间占用。缩容的条件是元素个数低于数组长度的10%并且缩容不考虑是否在做redis持久化。 不用考虑bgsave主要是因为我们的缩容的内存都是已经使用过的缩容的时候可以直接置空而且由于申请的内存比较小同时会释放掉一些已经使用的内存不会增大系统的压力。 rehash步骤 1、为ht[1] 分配空间让字典同时持有ht[0]和ht[1]两个哈希表 2、定时维持一个索引计数器变量rehashidx并将它的值设置为0表示rehash 开始 3、在rehash进行期间每次对字典执行CRUD操作时程序除了执行指定的操作以外还会将ht[0]中的数据rehash 到ht[1]表中并且将rehashidx加一 4、当ht[0]中所有数据转移到ht[1]中时将rehashidx 设置成-1表示rehash 结束 采用渐进式rehash 的好处在于它采取分而治之的方式避免了集中式rehash 带来的庞大计算量。特别的在进行rehash时只能对h[0]元素减少的操作如查询和删除而查询是在两个哈希表中查找的而插入只能在ht[1]中进行ht[1]也可以查询和删除。 5、将ht[0]释放然后将ht[1]设置成ht[0]最后为ht[1]分配一个空白哈希表。 过程如下图 6 set数据结构 Redis 的集合相当于Java中的 HashSet它内部的键值对是无序、唯一的。它的内部实现相当于一个特殊的字典字典中所有的 value 都是一个值 NULL。集合Set类型底层编码包括hashtable和inset。 当存储的数据同时满足下面这样两个条件的时候Redis 就采用整数集合intset来实现set这种数据类型 存储的数据都是整数存储的数据元素个数小于512个 当不能同时满足这两个条件的时候Redis 就使用dict来存储集合中的数据 hashtable在上面介绍过了我们就只介绍inset。 inset结构体 typedef struct intset {uint32_t encoding;// length就是数组的实际长度uint32_t length;// contents 数组是实际保存元素的地方数组中的元素有以下两个特性// 1.没有重复元素// 2.元素在数组中从小到大排列int8_t contents[]; } intset;// encoding 的值可以是以下三个常量的其中一个 #define INTSET_ENC_INT16 (sizeof(int16_t)) #define INTSET_ENC_INT32 (sizeof(int32_t)) #define INTSET_ENC_INT64 (sizeof(int64_t)) inset的查询 intset是一个有序集合查找元素的复杂度为O(logN)采用二分法但插入时不一定为O(logN)因为有可能涉及到升级操作。比如当集合里全是int16_t型的整数这时要插入一个int32_t那么为了维持集合中数据类型的一致那么所有的数据都会被转换成int32_t类型涉及到内存的重新分配这时插入的复杂度就为O(N)了。是intset不支持降级操作。 inset是有序不要和我们zset搞混zset是设置一个score来进行排序而inset这里只是单纯的对整数进行升序而已 7 Zset数据结构 Zset有序集合和set集合有着必然的联系他保留了集合不能有重复成员的特性但不同的是有序集合中的元素是可以排序的但是它和列表的使用索引下标作为排序依据不同的是它给每个元素设置一个分数作为排序的依据。 zet的底层编码有两种数据结构一个ziplist一个是skiplist。 Zset也使用了ziplist做了排序所以下面讲一下ziplist如何做排序。 ziplist做排序 每个集合元素使用两个紧挨在一起的压缩列表节点来保存第一个节点保存元素的成员member而第二个元素则保存元素的分值score。 存储结构图如下一目了然 skiplist跳表 结构体如下skiplist是与dict结合来使用的这个结构比较复杂。 /** 跳跃表*/ typedef struct zskiplist {// 头节点尾节点struct zskiplistNode *header, *tail;// 节点数量unsigned long length;// 目前表内节点的最大层数int level; } zskiplist;/** 跳跃表节点*/ typedef struct zskiplistNode {// member 对象robj *obj;// 分值double score;// 后退指针struct zskiplistNode *backward;// 层struct zskiplistLevel {// 前进指针struct zskiplistNode *forward;// 这个层跨越的节点数量unsigned int span;} level[]; } zskiplistNode; 跳表是什么 我们先看下链表 如果想查找到node5需要从node1查到node5查询耗时 但如果在node上加上索引 这样通过索引就能直接从node1查找到node5 redis跳跃表 让我们再看下redis的跳表结构图太复杂直接从网上找了张图说明 header:指向跳跃表的表头节点通过这个指针程序定位表头节点的时间复杂度就为O(1)tail:指向跳跃表的表尾节点,通过这个指针程序定位表尾节点的时间复杂度就为O(1)level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)通过这个属性可以再O(1)的时间复杂度内获取层高最好的节点的层数length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)通过这个属性程序可以再O(1)的时间复杂度内返回跳跃表的长度。 结构右方的是四个 zskiplistNode结构,该结构包含以下属性 层(level): 节点中用L1、L2、L3等字样标记节点的各个层,L1代表第一层,L2代表第二层,以此类推。 每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离(跨度越大、距离越远)。在上图中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。 每次创建一个新跳跃表节点的时候,程序都根据幂次定律(powerlaw,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”。 后退(backward)指针 节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。与前进指针所不同的是每个节点只有一个后退指针因此每次只能后退一个节点。 分值(score): 各个节点中的1.0、2.0和3.0是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。 成员对象(oj): 各个节点中的o1、o2和o3是节点所保存的成员对象。在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值却可以是相同的:分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向)。 五 三大特殊数据类型 1 geospatial地理位置 1.geospatial将指定的地理空间位置纬度、经度、名称添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。 2.sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的以形成一个独特的52位整数。sorted set的double score可以代表一个52位的整数而不会失去精度。有兴趣的同学可以学习一下Geohash技术使用二分法构建唯一的二进制串 3.有效的经度是-180度到180度有效的纬度是-85.05112878度到85.05112878度 应用场景 查看附近的人微信位置共享地图上直线距离的展示 2 Hyperloglog基数 什么是基数 不重复的元素 hyperloglog 是用来做基数统计的其优点是输入的提及无论多么大hyperloglog使用的空间总是固定的12KB 利用12KB它可以计算2^64个不同元素的基数非常节省空间但缺点是估算的值可能存在误差 应用场景 网页统计UV 浏览用户数量同一天同一个ip多次访问算一次访问目的是计数而不是保存用户 传统的方式set保存用户的id可以统计set中元素数量作为标准判断。 但如果这种方式保存大量用户id会占用大量内存我们的目的是为了计数而不是去保存id。 3 Bitmaps位存储 Redis提供的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构实际上就是字符串但是它可以对字符串的位进行操作。 可以把Bitmaps想象成一个以位为单位数组数组中的每个单元只能存0或者1数组的下标在bitmaps中叫做偏移量。单个bitmaps的最大长度是512MB即2^32个比特位。 应用场景 两种状态的统计都可以使用bitmaps例如统计用户活跃与非活跃数量、登录与非登录、上班打卡等等。 六 Redis事务 事务本质一组命令的集合 1 数据库事务与redis事务 数据库的事务 数据库事务通过ACID原子性、一致性、隔离性、持久性来保证。 数据库中除查询操作以外插入(Insert)、删除(Delete)和更新(Update)这三种操作都会对数据造成影响因为事务处理能够保证一系列操作可以完全地执行或者完全不执行因此在一个事务被提交以后该事务中的任何一条SQL语句在被执行的时候都会生成一条撤销日志(Undo Log)。 redis事务 redis事务提供了一种“将多个命令打包 然后一次性、按顺序地执行”的机制 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后 才会继续处理其他客户端的其他命令。 Redis中一个事务从开始到执行会经历开始事务muiti、命令入队和执行事务(exec)三个阶段事务中的命令在加入时都没有被执行直到提交时才会开始执行(Exec)一次性完成。 一组命令中存在两种错误不同处理方式 1.代码语法错误编译时异常所有命令都不执行 2.代码逻辑错误运行时错误其他命令可以正常执行 该点不保证事务的原子性 为什么redis不支持回滚来保证原子性 这种做法的优点 Redis 命令只会因为错误的语法而失败并且这些问题不能在入队时发现或是命令用在了错误类型的键上面这也就是说从实用性的角度来说失败的命令是由编程错误造成的而这些错误应该在开发的过程中被发现而不应该出现在生产环境中。因为不需要对回滚进行支持所以 Redis 的内部可以保持简单且快速。 **鉴于没有任何机制能避免程序员自己造成的错误 并且这类错误通常不会在生产环境中出现 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。 事务监控 悲观锁认为什么时候都会出现问题无论做什么操作都会加锁。 乐观锁认为什么时候都不会出现问题所以不会上锁更新数据的时候去判断一下在此期间是否有人修改过这个数据。 使用cas实现乐观锁 redis使用watch key监控指定数据相当于加乐观锁 watch保证事务只能在所有被监视键都没有被修改的前提下执行 如果这个前提不能满足的话事务就不会被执行。 watch执行流程 七 Redis持久化 Redis是一种内存型数据库一旦服务器进程退出数据库的数据就会丢失为了解决这个问题Redis供了两种持久化的方案将内存中的数据保存到磁盘中避免数据的丢失 两种持久化方式快照RDB文件和追加式文件AOF文件下面分别为大家介绍两种方式的原理。 RDB持久化方式会在一个特定的间隔保存那个时间点的数据快照。AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致以追加的方式进行保存。Redis的持久化是可以禁用的就是说你可以让数据的生命周期只存在于服务器的运行时间里。 两种方式的持久化是可以同时存在的但是当Redis重启时AOF文件会被优先用于重建数据。 1 RDB持久化 RDB持久化产生的文件是一个经过压缩的二进制文件这个文件可以被保存到硬盘中可以通过这个文件还原数据库的状态它可以手动执行也可以在redis.conf配置文件中配置定时执行。 工作原理 在进行RDB时redis的主进程不会做io操作会fork一个子进程来完成该操作 Redis 调用forks。同时拥有父进程和子进程。子进程将数据集写入到一个临时 RDB 文件中。当子进程完成对新 RDB 文件的写入时Redis 用新 RDB 文件替换原来的 RDB 文件并删除旧的 RDB 文件。 这种工作方式使得 Redis 可以从写时复制copy-on-write机制中获益(因为是使用子进程进行写操作而父进程依然可以接收来自客户端的请求) 触发机制 在Redis中RDB持久化的触发分为两种自己手动触发与自动触发。 主动触发 save命令是同步的命令会占用主进程会造成阻塞阻塞所有客户端的请求bgsave bgsave是异步进行进行持久化的时候redis还可以将继续响应客户端请求 bgsave和save对比 自动触发 1、save自动触发配置见下面配置满足m秒内修改n次key触发rdb # 时间策略 save m n m秒内修改n次key触发rdb save 900 1 save 300 10 save 60 10000# 文件名称 dbfilename dump.rdb# 文件保存路径 dir /home/work/app/redis/data/# 如果持久化出错主进程是否停止写入 stop-writes-on-bgsave-error yes# 是否压缩 rdbcompression yes# 导入时是否检查 rdbchecksum yes 2、从节点全量复制时主节点发送rdb文件给从节点完成复制操作主节点会触发bgsave命令 3、执行flushall命令会触发rdb 4、退出redis且没有开启aof时 优点 1、RDB 的内容为二进制的数据占用内存更小更紧凑更适合做为备份文件 2、RDB 对灾难恢复非常有用它是一个紧凑的文件可以更快的传输到远程服务器进行 Redis 服务恢复 3、RDB 可以更大程度的提高 Redis 的运行速度因为每次持久化时 Redis 主进程都会 fork() 一个子进程进行数据持久化到磁盘Redis 主进程并不会执行磁盘 I/O 等操作 4、与 AOF 格式的文件相比RDB 文件可以更快的重启。 缺点 1、因为 RDB 只能保存某个时间间隔的数据如果中途 Redis 服务被意外终止了则会丢失一段时间内的 Redis 数据。 2、RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大fork() 可能很耗时并且如果数据集很大且 CPU 性能不佳则可能导致 Redis 停止为客户端服务几毫秒甚至一秒钟。 2 AOFAppend Only File 以日志的形式来记录每个写的操作将Redis执行过的所有指令记录下来读操作不记录只许追加文件但不可以改写文件redis启动之初会读取该文件重新构建数据换言之redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。 AOF配置项 # 默认不开启aof 而是使用rdb的方式 appendonly no# 默认文件名 appendfilename appendonly.aof# 每次修改都会sync 消耗性能 # appendfsync always # 每秒执行一次 sync 可能会丢失这一秒的数据 appendfsync everysec # 不执行 sync ,这时候操作系统自己同步数据速度最快 # appendfsync no AOF的整个流程大体来看可以分为两步一步是命令的实时写入如果是appendfsync everysec 配置会有1s损耗第二步是对aof文件的重写。 AOF 重写机制 随着Redis的运行AOF的日志会越来越长如果实例宕机重启那么重放整个AOF将会变得十分耗时而在日志记录中又有很多无意义的记录比如我现在将一个数据 incr一千次那么就不需要去记录这1000次修改只需要记录最后的值即可。所以就需要进行 AOF 重写。 Redis 提供了bgrewriteaof指令用于对AOF日志进行重写该指令运行时会开辟一个子进程对内存进行遍历然后将其转换为一系列的 Redis 的操作指令再序列化到一个日志文件中。完成后再替换原有的AOF文件至此完成。 同样的也可以在redis.config中对重写机制的触发进行配置 通过将no-appendfsync-on-rewrite设置为yes开启重写机制auto-aof-rewrite-percentage 100意为比上次从写后文件大小增长了100%再次触发重写 auto-aof-rewrite-min-size 64mb意为当文件至少要达到64mb才会触发制动重写。 触发方式 手动触发bgrewriteaof自动触发 就是根据配置规则来触发当然自动触发的整体时间还跟Redis的定时任务频率有关系。 优点 1、数据安全aof 持久化可以配置 appendfsync 属性有 always每进行一次 命令操作就记录到 aof 文件中一次。 2、通过 append 模式写文件即使中途服务器宕机可以通过 redis-check-aof 工具解决数据一致性问题。 3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前文件过大时会对命令 进行合并重写可以删除其中的某些命令比如误操作的 flushall) 缺点 1、AOF 文件比 RDB 文件大且恢复速度慢。 2、数据集大的时候比 rdb 启动效率低。 3 rdb与aof对比 八 发布与订阅 redis发布与订阅是一种消息通信的模式发送者pub发送消息订阅者sub接收消息。 redis通过PUBLISH和SUBSCRIBE等命令实现了订阅与发布模式这个功能提供两种信息机制分别是订阅/发布到频道、订阅/发布到模式的客户端。 1 频道channel 订阅 发布 完整流程 发布者发布消息 发布者向频道channel:1发布消息hi 127.0.0.1:6379 publish channel:1 hi (integer) 1 订阅者订阅消息 127.0.0.1:6379 subscribe channel:1 Reading messages... (press Ctrl-C to quit) 1) subscribe // 消息类型 2) channel:1 // 频道 3) hi // 消息内容 执行subscribe后客户端会进入订阅状态仅可以使subscribe、unsubscribe、psubscribe和punsubscribe这四个属于发布/订阅之外的命令 订阅频道后的客户端可能会收到三种消息类型 subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称第三个是当前客户端订阅的频道数量。message。表示接收到的消息第二个值表示产生消息的频道名称第三个值是消息的内容。unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称第三个值是当前客户端订阅的频道数量当此值为0时客户端会退出订阅状态之后就可以执行其他非发布/订阅模式的命令了。 数据结构 基于频道的发布订阅模式是通过字典数据类型实现的 struct redisServer {// ...dict *pubsub_channels;// ... }; 其中字典的键为正在被订阅的频道 而字典的值则是一个链表 链表中保存了所有订阅这个频道的客户端。 订阅 当使用subscribe订阅时在字典中找到频道key如没有则创建并将订阅的client关联在链表后面。 当client 10执行subscribe channel1 channel2 channel3时会将client 10分别加到 channel1 channel2 channel3关联的链表尾部。 发布 发布时根据key找到字典汇总key的地址然后将msg发送到关联的链表每一台机器。 退订 遍历关联的链表将指定的地址删除即可。 2 模式pattern pattern使用了通配符的方式来订阅 通配符中?表示1个占位符表示任意个占位符(包括0)?表示1个以上占位符。 所以当使用 publish命令发送信息到某个频道时 不仅所有订阅该频道的客户端会收到信息 如果有某个/某些模式和这个频道匹配的话 那么所有订阅这个/这些频道的客户端也同样会收到信息。 订阅发布完整流程 发布者发布消息 127.0.0.1:6379 publish b m1 (integer) 1 127.0.0.1:6379 publish b1 m1 (integer) 1 127.0.0.1:6379 publish b11 m1 (integer) 1 订阅者订阅消息 127.0.0.1:6379 psubscribe b* Reading messages... (press Ctrl-C to quit) 1) psubscribe 2) b* 3) (integer) 3 1) pmessage 2) b* 3) b 4) m1 1) pmessage 2) b* 3) b1 4) m1 1) pmessage 2) b* 3) b11 4) m1数据结构 pattern属性是一个链表链表中保存着所有和模式相关的信息。 struct redisServer {// ...list *pubsub_patterns;// ... }; // 链表中的每一个节点结构如下保存着客户端与模式信息 typedef struct pubsubPattern {redisClient *client;robj *pattern; } pubsubPattern; 数据结构图如下 订阅 当有信的订阅时会将订阅的客户端和模式信息添加到链表后面。 发布 当发布者发布消息时首先会发送到对应的频道上在遍历模式列表根据key匹配模式匹配成功将消息发给对应的订阅者。 完成的发布伪代码如下 def PUBLISH(channel, message):# 遍历所有订阅频道 channel 的客户端for client in server.pubsub_channels[channel]:# 将信息发送给它们send_message(client, message)# 取出所有模式以及订阅模式的客户端for pattern, client in server.pubsub_patterns:# 如果 channel 和模式匹配if match(channel, pattern):# 那么也将信息发给订阅这个模式的客户端send_message(client, message) 退订 使用punsubscribe可以将订阅者退订将改客户端移除出链表。 九 主从复制 什么是主从复制 主从复制是指将一台Redis服务器的数据复制到其他的Redis服务器。 2.前者称为主节点(master)后者称为从节点(slave)数据的复制是单向的只能由主节点到从节点 默认情况下每台redis服务器都是主节点且一个主节点可以有多个从节点或者没有但一个从节点只有一个主 主从复制的作用主要包括 数据冗余主从复制实现了数据的热备份是持久化之外的一种数据冗余方式。故障恢复当主节点出现问题时可以由从节点提供服务实现快速的故障恢复实际上是一种服务的冗余。负载均衡在主从复制的基础上配合读写分离可以由主节点提供写服务由从节点提供读服务即写Redis数据时应用连接主节点读Redis数据时应用连接从节点分担服务器负载尤其是在写少读多的场景下通过多个从节点分担读负载可以大大提高Redis服务器的并发量。高可用基石除了上述作用以外主从复制还是哨兵和集群能够实施的基础因此说主从复制是Redis高可用的基础。 主从库采用的是读写分离的方式 1 原理 分为全量复制与增量复制 全量复制发生在第一次复制时 增量复制只会把主从库网络断连期间主库收到的命令同步给从库 2 全量复制的三个阶段 第一阶段是主从库间建立连接、协商同步的过程。 主要是为全量复制做准备。从库和主库建立起连接并告诉主库即将进行同步主库确认回复后主从库间就可以开始同步了。 具体来说从库给主库发送 psync 命令表示要进行数据同步主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。runID是每个 Redis 实例启动时都会自动生成的一个随机 ID用来唯一标记这个实例。当从库和主库第一次复制时因为不知道主库的 runID所以将 runID 设为“”。offset此时设为 -1表示第一次复制。主库收到 psync 命令后会用 FULLRESYNC 响应命令带上两个参数主库 runID 和主库目前的复制进度 offset返回给从库。从库收到响应后会记录下这两个参数。这里有个地方需要注意FULLRESYNC 响应表示第一次复制采用的全量复制也就是说主库会把当前所有的数据都复制给从库。 第二阶段主库将所有数据同步给从库。 从库收到数据后在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。 具体来说主库执行 bgsave 命令生成 RDB 文件接着将文件发给从库。从库接收到 RDB 文件后会先清空当前数据库然后加载 RDB 文件。这是因为从库在通过 replicaof 命令开始和主库同步前可能保存了其他数据。为了避免之前数据的影响从库需要先把当前数据库清空。在主库将数据同步给从库的过程中主库不会被阻塞仍然可以正常接收请求。否则Redis 的服务就被中断了。但是这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性主库会在内存中用专门的 replication buffer记录 RDB 文件生成后收到的所有写操作。 第三个阶段主库会把第二阶段执行过程中新收到的写命令再发送给从库。 具体的操作是当主库完成 RDB 文件发送后就会把此时 replication buffer 中的修改操作发给从库从库再重新执行这些操作。这样一来主从库就实现同步了。 十 哨兵机制 哨兵的核心功能是主节点的自动故障转移 下图是一个典型的哨兵集群监控的逻辑图 Redis Sentinel包含了若个Sentinel节点这样做也带来了两个好处 对于节点的故障判断是由多个Sentinel节点共同完成这样可以有效地防止误判即使个别Sentinel节点不可用整个Sentinel集群依然是可用的。 哨兵实现了一下功能 监控每个Sentinel节点会对数据节点Redis master/slave 节点和其余Sentinel节点进行监控通知Sentinel节点会将故障转移的结果通知给应用方故障转移实现slave晋升为master并维护后续正确的主从关系配置中心在Redis Sentinel模式中客户端在初始化的时候连接的是Sentinel节点集合从中获取主节点信息 其中监控和自动故障转移功能使得哨兵可以及时发现主节点故障并完成转移而配置中心和通知功能则需要在与客户端的交互中才能体现。 1 原理 监控 Sentinel节点需要监控master、slave以及其它Sentinel节点的状态。这一过程是通过Redis的pub/sub系统实现的。Redis Sentinel一共有三个定时监控任务完成对各个节点发现和监控 监控主从拓扑信息每隔10秒每个Sentinel节点会向master和slave发送INFO命令获取最新的拓扑结构Sentinel节点信息交换每隔2秒每个Sentinel节点会向Redis数据节点的__sentinel__:hello频道上发送自身的信息以及对主节点的判断信息。这样Sentinel节点之间就可以交换信息节点状态监控每隔1秒每个Sentinel节点会向master、slave、其余Sentinel节点发送PING命令做心跳检测来确认这些节点当前是否可达 主观/客观下线 主观下线 每个Sentinel节点每隔1秒会对数据节点发送ping命令做心跳检测当这些节点超过down-after-milliseconds没有进行有效回复时Sentinel节点会对该节点做失败判定这个行为叫做主观下线。 客观下线 客观下线是指当大多数Sentinel节点都认为master节点宕机了那么这个判定就是客观的叫做客观下线。 那么这个大多数是指多少呢这其实就是分布式协调中的quorum判定了大多数就是过半数比如哨兵数量是5那么大多数就是5/213个哨兵数量是10大多数就是10/216个。 注Sentinel节点的数量至少为3个否则不满足quorum判定条件。 哨兵选举 如果发生了客观下线那么哨兵节点会选举出一个Leader来进行实际的故障转移工作。Redis使用了Raft算法来实现哨兵领导者选举大致思路如下 每个Sentinel节点都有资格成为领导者当它主观认为某个数据节点宕机后会向其他Sentinel节点发送sentinel is-master-down-by-addr命令要求自己成为领导者收到命令的Sentinel节点如果没有同意过其他Sentinel节点的sentinelis-master-down-by-addr命令将同意该请求否则拒绝每个Sentinel节点只有1票如果该Sentinel节点发现自己的票数已经大于等于MAX(quorum, num(sentinels)/21)那么它将成为领导者如果此过程没有选举出领导者将进入下一次选举。 故障转移 选举出的Leader Sentinel节点将负责故障转移也就是进行master/slave节点的主从切换。故障转移首先要从slave节点中筛选出一个作为新的master主要考虑以下slave信息 跟master断开连接的时长如果一个slave跟master的断开连接时长已经超过了down-after-milliseconds的10倍外加master宕机的时长那么该slave就被认为不适合选举为masterslave的优先级配置slave priority参数值越小优先级就越高复制offset当优先级相同时哪个slave复制了越多的数据offset越靠后优先级越高run id如果offset和优先级都相同则哪个slave的run id越小优先级越高。 接着筛选完slave后 会对它执行slaveof no one命令让其成为主节点。 最后Sentinel领导者节点会向剩余的slave节点发送命令让它们成为新的master节点的从节点复制规则与parallel-syncs参数有关。 Sentinel节点集合会将原来的master节点更新为slave节点并保持着对其关注当其恢复后命令它去复制新的主节点。 注Leader Sentinel节点会从新的master节点那里得到一个configuration epoch本质是个version版本号每次主从切换的version号都必须是唯一的。其他的哨兵都是根据version来更新自己的master配置。 十一 缓存穿透、击穿、雪崩 1 缓存穿透 问题来源 缓存穿透是指缓存和数据库中都没有的数据而用户不断发起请求。由于缓存是不命中时被动写的并且出于容错考虑如果从存储层查不到数据则不写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。在流量大时可能DB就挂掉了要是有人利用不存在的key频繁攻击我们的应用这就是漏洞。 如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者攻击会导致数据库压力过大。 解决方案 接口层增加校验如用户鉴权校验id做基础校验id0的直接拦截从缓存取不到的数据在数据库中也没有取到这时也可以将key-value对写为key-null缓存有效时间可以设置短点如30秒设置太长会导致正常情况也没法使用。这样可以防止攻击用户反复用同一个id暴力攻击。布隆过滤器。类似于一个hash set用于快速判某个元素是否存在于集合中其典型的应用场景就是快速判断一个key是否存在于某容器不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。 2 缓存击穿 问题来源 缓存击穿是指缓存中没有但数据库中有的数据一般是缓存时间到期这时由于并发用户特别多同时读缓存没读到数据又同时去数据库去取数据引起数据库压力瞬间增大造成过大压力。 解决方案 1、设置热点数据永远不过期。 2、接口限流与熔断降级。重要的接口一定要做好限流策略防止用户恶意刷接口同时要降级准备当接口中的某些服务不可用时候进行熔断失败快速返回机制。 3、加互斥锁 3 缓存雪崩 问题来源 缓存雪崩是指缓存中数据大批量到过期时间而查询数据量巨大引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据缓存雪崩是不同数据都过期了很多数据都查不到从而查数据库。 解决方案 缓存数据的过期时间设置随机防止同一时间大量数据过期现象发生。如果缓存数据库是分布式部署将热点数据均匀分布在不同的缓存数据库中。设置热点数据永远不过期 文章最后 每一项技术深挖都是一个庞大的体系学海无涯共勉。 原文链接 本文为阿里云原创内容未经允许不得转载。
http://www.pierceye.com/news/755614/

相关文章:

  • 搭建网站需要什么软件上海在线
  • led灯外贸网站建设网站代码怎么优化
  • 网站建设维护什么意思江苏网络推广专员
  • 潍坊网站开发asp培训珠海市网站建设公司
  • 用什么做响应式网站建行个人余额查询网站
  • 做网站网站代理怎么找客源企业团建公司
  • 电子商务网站开发实战济南兼职做网站
  • 怎样创建网站视频学历提升的重要性
  • 百度搜索引擎录入网站1_ 掌握网站开发的基本流程 要求:熟悉网站开发与设计的基本流程.
  • 广州做网站建设如何在别人网站挂黑链
  • 宁德北京网站建设任丘建设银行网站
  • 积极加强网站建设连锁会员管理系统
  • 河南做外贸网站的公司简介wordpress做教育网站
  • 兴城做网站推广的企业网站后台管理软件
  • 自定义优定软件网站建设申请永久网站空间
  • 免费发布信息的网站平台怎么做网站统计
  • 制作网站的过程是对信息的龙海市住房和城乡建设局网站
  • 鱼台县建设局网站免费ppt模板制作软件
  • 质量好网站建设多少钱黄冈网站建设哪家快些
  • 使用阿里云部署wordpressseo搜索排名影响因素主要有
  • 大连制作网站建站教程图解
  • 百度的合作网站有哪些网站建设费用写创意
  • 建设个人网站ip护肤品网页设计图片
  • 德州网站建设优化金阳龙泉苑网站建设
  • 建站公司最新价格网站素材网
  • 高品质的网站开发公优酷网站谁做的
  • 广西兴业县建设局网站湖北天健建设集团有限公司网站
  • 学多久可以做网站 知乎中国100强企业名单公布
  • 江阴网站优化公司开源的 二次网站开发
  • 淄博网站建设相关文章wordpress登录网页