《redis设计与实现》 作者:黄健宏

读书笔记

一、前言

什么是redis:
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。简而言之redis就是放在远程网络上的一个key-value数据结构。
redis的使用场景:
Redis可以用来做cache,也可以用来做持久化db,甚至用来做队列。队列就是存储临时性数据,而cache主要存储那些用户频繁使用的数据。当然也可以来做db(不建议,持久化存储代价比较高)

二、简单动态字符串

简单动态字符串 simple dynamic string, sds, 用作redis的默认字符串表示;
在redis中应用有:列表键,发布与订阅,慢查询,监视器,多个客户端,客户端的输出缓冲区。
数据结构:
struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中剩余可用空间的长度 int free; // 数据空间 char buf[]; // 依然以’\0’结尾 };

SDS与C字符串的区别
  常数复杂度获取字符串长度-----sds.len
  l杜绝缓冲区溢出-----通过free的长度来判断是否有充足的空间
  减少修改字符串时带来的内存重分配次数----free够的时候,不需要重分配,只有free不足的时候需要扩展。如果对字符串收缩也不需要立刻回收空间,只需要修改len和free的属性即可。----空间预分配和惰性空间释放
  兼容部分C字符串函数----由于是以”\0”结尾
  二进制安全-----c字符串不能保存图片、音频、视频等二进制数据
   SDS相对C字符串虽然有些操作更快更便捷,但是这是以事先预分配超过需求的空间,以及记录len和free来完成的。这个过程有点以牺牲空间换时间的方法。而且这个len和free大小设置也是一门技术活!

C字符串

SDS

获取字符串长度的复杂度为O(N)

获取字符串长度的复杂度是O(1)

API是不安全的,可能造成缓冲区溢出

API是安全的,不会造成缓冲区溢出

修改字符串长度N次必然执行N次内存重分配

修改字符串长度N次最多需要执行N次内存重分配

只能保存文本数据

可以保存文本或二进制数据

可以使用所有<string.h>库中的函数

可以使用一部分<string.h>库中的函数

三、链表

数据结构:
typedef struct listNode { // 前置节点 struct listNode *prev;
// 后置节点 struct listNode *next;
// 节点的值 void *value; } listNode; typedef struct list { // 表头节点 listNode *head;
// 表尾节点 listNode *tail;
// 节点值复制函数 void *(*dup)(void *ptr);
// 节点值释放函数 void (*free)(void *ptr); // 节点值对比函数 int (*match)(void *ptr, void *key); // 链表所包含的节点数量 unsigned long len; } list;
有prev, next, head, tail, len;
双端、无环、带表头指针和表尾指针、带长度计数器、多态(可以通过list结构的dup, free, match属性为节点值设置类型特定函数);

四、字典

应用:在redis中的应用主要有db(数据存储在字典中),哈希键
字典数据结构:
typedef struct dict { // 类型特定函数 dictType *type; // 私有数据 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 当 rehash 不在进行时,值为 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 目前正在运行的安全迭代器的数量 int iterators; /* number of iterators currently running */ } dict;

hash表数据结构:
typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used; } dictht

hash节点:
typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,形成链表 struct dictEntry *next;
} dictEntry;
/* * 字典类型特定函数 */ typedef struct dictType { // 计算哈希值的函数 unsigned int (*hashFunction)(const void *key); // 复制键的函数 void *(*keyDup)(void *privdata, const void *key); // 复制值的函数 void *(*valDup)(void *privdata, const void *obj); // 对比键的函数 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 销毁键的函数 void (*keyDestructor)(void *privdata, void *key); // 销毁值的函数 void (*valDestructor)(void *privdata, void *obj); } dictType;

渐近式rehash:
hash表的负载因子 = hash节点个数/hash表的长度
为了避免rehash(hash节点个数可能成百上千万个)对服务器性能造成影响,服务器不是一次性将ht[0]里面的所有键值对全部rehash到ht[1],  而是分多次、渐进式地将ht[0]里面的键值对慢慢地rehash到ht[1].
  渐进式rehash的基本步骤是:
  (1)为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
  (2)在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,标识rehash开始。
  (3)在rehash进行期间,每次对字典执行添加、查找、删除或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1], 当rehash工作完成之后,程序将rehashidx属性的值增一。
  (4)随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx的属性的值设为-1,表示rehash操作已完成。
  渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到字典的每个添加删除查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。

满足以下任意条件, hash表执行扩展操作:
1)服务器目前没有执行bgsave, 或bgrewriteaof命令,且负载因子大于1;
2)服务器正在执行bgsave, 或bgrewriteaof命令,且负载因子大于5;

每个字典带有两个hash表, 一个平时使用, 一个rehash时使用;

五、跳跃表

跳跃表(skiplist)是一种有序数据结构,它通过在某个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
数据结构:
/* * 跳跃表节点 */ typedef struct zskiplistNode { // 成员对象 robj *obj; // 分值 double score; // 后退指针 struct zskiplistNode *backward; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 跨度---前进指针所指向节点与当前节点的距离 unsigned int span; } level[]; } zskiplistNode; /* * 跳跃表 */ typedef struct zskiplist { // 表头节点和表尾节点 struct zskiplistNode *header, *tail; // 表中节点的数量 unsigned long length; // 表中层数最大的节点的层数 int level; } zskiplist;

类似红黑树,但是实现较红黑树简单。排序或者有序查找可以达到二分查找的效率,该数据结构是以空间换时间。
 操作及复杂度:
  zadd---zslinsert---平均O(logN), 最坏O(N)
  zrem---zsldelete---平均O(logN), 最坏O(N)
  zrank--zslGetRank---平均O(logN), 最坏O(N)

六、数据集合

整数集合是集合键的底层实现之一, 当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。
数据结构:
typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; } intset;
操作及复杂度:
  sadd:intsetAdd---O(1)
  smembers:intsetGetO(1)---O(N)
  srem:intsetRemove---O(N)
  slen:intsetlen ---O(1)
升级:每当一个新元素要添加到集合里,并且新元素的类型比集合里现有所有元素的类型都要长时,整数集合需要先进行升级。
因为引发升级的新元素,比现有所有元素长度都大,所以这个新元素要么比现在所有元素都大,要么比所有元素都小(负数),所以新元素一定放在开头或结尾处。
不支持降级操作;

七、压缩列表

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。
压缩列表是列表键和hash键的底层实现之一
连锁更新:由于每个节点的previous_entry_length都记录的前一个节点的长度:
如果前一节点长度小于254,则previous_entry_length用1字节空间保存;
如果前一节点长度大于254,则previous_entry_length用5字节空间保存;
如果有多个节点的长度都是250-253之间, 那么 更新第一个时,就会引发连锁更新;
压缩列表如何省空间:存储于一块连续的内存,元素与元素之间没有空隙;

八、Redis对象

Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据库建立了一个对象系统,比如字符串对象,列表对象,集合对象,hash表对象,有序集合对等
  使用对象的好处在于:
  (1)我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率—对应redisObject中的encoding
  (2)Redis的对象还实现了基于引用计数计数的内存回收机制---对应redisObject中的refCount
  (3)Redis还通过引用计数实现了对象共享机制
  (4)Redis对象带有访问时间记录信息,该信息可以计算数据库键的空转时长,从而确定在内存不足的情况下是否优先删除
数据结构:
typedef struct redisObject { // 类型 对象采用何种数据结构作为对象底层实现 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ // 引用计数 int refcount; // 指向实际值的指针 void *ptr; } robj;

字符串对象编码:
int, embstr(字符串小于等于32字节), raw
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构。而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。正因为此,所以embstr编码比raw编码在保存短字符串方面更加高效(embstr比raw少一次分配,少一次释放,而且embstr的查找连续内存更加高效)。
列表对象编码:
包括ziplist和linkedlist
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
  列表对象保存的所有字符串元素的长度都小于64个字节;
  列表对象保存的元素数量少于512个;
不能同时满足这两个条件的列表对象需要使用linkedlist
哈希对象编码
  哈希对象的编码可以是ziplist(压缩链表)或者hashtable(字典)。当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:
  哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
  哈希对象保存的键值对数量小于512个;
 如果上述两个条件不能同时满足的时候,hash对象采用REDIS_ENCODING_HT的编码方式。
集合对象编码
  intset, hashTable
当集合对象同时满足以下两个条件时,使用intset编码:
当集合对象保存的所有元素都是整数值;
集合对象保存的元素数量不超过512个的时候;
不满足以上条件采用hashTable的编码方式。
有序集合对象编码
  有序集合zset的编码方式为skiplist或者ziplist。
  zset同时采用了skiplist和dict两个数据结构来实现有序集合。Skiplist有利于有序查找,而dict有利于范围查找。因为他们会共享元素的成员和分值,故并不会造成任何数据重复,只是多了一些指针,所以不会照成内存浪费。
 当有序集合对象可以同时满足以下两个条件时,对象使用ziplist编码:
  有序集合保存的元素数量小于128个;
  有序集合保存的所有元素成员的长度都小于64个字节;
不能满足以上两个条件的有序集合对象将使用skiplist编码。
对象的空转时长
  对象有一个空转时长的lru属性,该属性记录了对象最后一次被命令程序访问的时间。OBJECT IDLETIME命令可以打印出给定键的空转时长,这一空转时长就是通过将当前时间减去键的值对象的lru时间计算出来的。
当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的部分键会优先被服务器释放,从而回收内存。
对象的引用计数---内存回收 & 对象共享
  因为C语言并不具备自动内存回收功能,所以redis对象系统构建了一个引用计数refcount技术来实现内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象进行内存回收。
  对象的引用计数信息会随着对象的使用状态而不断变化:

  •   在创建一个新对象时,引用计数的值会被初始化为1;
  •   当对象被一个新程序使用时,它的引用计数值会被增1
  •   当对象不再被一个程序使用时,它的引用计数会减1
  •   当对象的引用计数为0时,对象所占用的内存会被释放。

对象共享:redis在初始化服务器时,创建1万个字符串对象(0-9999),当服务器需要用到时,用共享这些对象,而不是新创建对象。

九、单机数据库

  服务器中的多个数据库是server中的redisDB *Db和dbnums属性决定的。
  可以通过select n来选择n号数据库,比如select 0来选择0号数据库。注意在flushdb操作之前一定要select切换到目标数据库。
数据库中过期键删除策略
  定时删除:在设置过期键的同时创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。(内存友好,耗cpu)
  惰性删除:放任过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,则保留。(cpu友好,占内存)
  定期删除:每隔一段时间,程序会对数据库进行一次检查,或者对过期字典检查,删除里面的过期键。至于要隔多久,一次检查多少数据库,删除多少过期键,则由算法决定。
  为了主从一致,从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来del命令,这种统一,中心化的过期键删除策略可以保证主从服务器数据的一致性。
数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典则负责保存键的过期时间;
从服务器即使发现过期键也不会主动删除,而是等待主节点发来del命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。

十、RDB持久化

save 阻塞; bgsave 子进程并发进行处理;
RDB文件的载入工作是在服务器启动时自动执行的; 如果服务器启用了AOF功能,那么服务器优先使用AOF来还原数据库;
save命令执行时, 客户端发送的所有命令都会被拒绝;bgsave执行时, 仍可以处理客户端的命令请求,但save, bgsave会被拒绝, bgrewriteaof会被延迟到bgsave执行完后再执行;
RDB文件是一个经过压缩的二进制文件,由多个部分组成。
对于不同类型的键值对, RDB文件会使用不同的方式来保存它们。

十一、AOF持久化

AOF持久化通过保存redis服务器执行的写命令来记录数据库状态;
Aof文件主要分为三步
  (1)       命令追加:服务器执行完一个写命令之后,会将命令追加到aof_buf缓冲区末尾
  (2)       写入文件:将其写入aof文件,此时有一部分暂时保存在一个内存缓冲区
  数据写入文件时,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。所以当没有到指定时限且缓冲区未满的时候,此时有一部分还停留在内存缓冲区。
  (3)       文件同步:将保存在内存缓冲区那部分强制写入硬盘。
持久化行为:always, everysec, no
操作系统,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区,等到缓存区空间被填满或者超过指定的时限后,才将缓冲区中的内容写入到磁盘;
AOF重写:aof文件重写是通过读取服务器当前的数据库状态来实现的,与现有的aof文件无关; 从数据库读取键的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是aof重写的原理;
aof_rewrite重写期间,服务器将无法处理客户端发来的命令请求; 所以要将aof重写放在子进程里,bg_rewriteaof; 但在aof重写期间,客户端发来的命令又修改数据库,导致aof文件和数据库不一致, AOF重写缓冲区,解决了这一问题;

十二、事件

Redis服务器是一个事件驱动程序。服务器处理的事件分为文件事件、时间事件。
  文件事件:Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
  时间事件:Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。
  简而言之:Redis服务主要是通过监听和处理各种事件来完成各种任务。包括客户端连接,处理客户端请求,返回客户端请求结果,关闭连接,定时备份,定时清理内存,定时关闭连接,定时清理过期键等操作。
   Redis文件事件通过使用I/O多路复用程序来监听多个套接字,文件事件处理器即实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。

IO多路复用程序将套接字放在一个队列里面,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字;
文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的过程也不会进行抢占。

13、客户端

I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命请求,并与多个客户端进行网络通信;
伪客户端:AOF文件或lua脚本; 普通客户端:
服务器状态结构使用clients链表连接起多个客户端状态,新添加的客户端状态会被放到链表的末尾;
客户端状态的flags属性使用不同标志来表示客户端的角色,以及客户端当前所处的状态;
输入缓存区记录了命令发送的请求,这个缓冲区的大小不能超过1G;
命令的参数和参数个数会被记录在客户端状态的argv和argc里,而cmd属性则记录了客户端要执行命令的实现函数;

14、服务器

一个命令从发送到完成主要经过以下步骤:1、客户端将命令请求发送给服务器;2、服务器读取命令请求并分析出命令参数;3、命令执行器根据参数查找命令的实现函数, 然后执行实现函数并得出命令回复;4、服务器将命令回复返回给客户端;
serverCron函数默认每100ms执行一次,它的工作主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等等。
服务器从启动到能够处理客户端的命令请求需要执行以下步骤:1、初始化服务器状态;2、载入服务器配置;3、初始化服务器数据结构;4、还原数据库状态;5、执行事件循环;

15、复制

slaveOf 主ip:主端口
Redis的复制功能分为同步和命令传播;
同步:1、从服务器发送sync命令;2、主服务器执行bgsave,生成rdb文件, 使用缓冲区记录之后的命令;3、rdb文件发给从服务器;4、缓冲区里的写命令发给从服务器;
命令传播:主服务器将自己执行的写命令发送给从服务器;
Redis2.8以前的复制功能不能高效的处理断线后复制情况,但redis2.8新添加的部分重同步功能可以解决这个问题;
旧版复制缺陷:中断重连后, 重新复制;
部分重同步通过复制偏移量、复制积压缓冲区、服务器运行ID三个部分来实现;
在复制操作刚开始的时候,从服务器会成为主服务器的客户端 ,并通过向主服务器发送命令请求来执行复制步骤,而在复制操作的后期,主从服务器会相互成为对方的客户端;
主服务器通过向从服务器传播命令来更新从服务器的状态,保持主服务器和从服务器一致,而从服务器则通过向主服务器发送命令(默认每秒1次)来进行心跳检测,以及命令丢失检测;

16、Sentinel

Sentinel是一个运行在特殊模式下的Redis服务器,它使用了和普通模式不同的命令表,所以Sentinel模式能够使用的命令和普通Redis服务器的命令不同。
Sentinel默认会以每10s一次的频率,通过命令连接向被监视的主服务器发送info命令,并通过分析info命令的回复来获取主服务器的当前信息;当主服务器处于下线状态,或者Sentinel正在对主服务器进行故障转移时,Sentinel向主服务器发送info命令改为每秒一次。
检查主观下线:sentinel的配置文件down-after-milliseconds 时间内,主服务器连续返回无效回复,则被sentinel标记为主观下线;
检查客观下线:向其他sentinel询问是否同意主服务器已下线, sentinel将统计其他sentinel同意主服务器已下线的数量, 当这一数量达到配置指定 的判断客观下线所需数量时, sentinel就把服务器标记为客观下线;
选举领头sentinel,进行故障转移:源sentinel向目标sentinel发送命令,要求将前者设为局部领头sentinel;先到先得;如果某个sentinel被半数以上的其他sentinel设为局部领头sentinel, 就为领头sentinel;
故障转移:选新的主服务器:优先级、复制偏移量、运行Id最小;修改从服务器的手复制目标为新的主服务器;将旧的主服务器变为从服务器,以便再次上线时使用;

17、集群

节点:节点通过握手将其他节点添加到自己所处的集群当中;
槽指派: 16384个槽;
节点只能使用0数据库;
重新分片:单个槽slot重新分片:1、让目标节点准备好从源节点导入属于槽slot的键值对;2、让源节点准备好将属于槽slot的键值对迁移到目标节点;3、向源节点发送命令,一次最多迁移count个键值对;4、对于步骤3的每个键名,向源节点发送命令,保证键原子地从源节点迁移到目标节点;5、重复3,4步;5、向集群中任一节点发送通知,slot指派给了目标节点, 这一指派消息会通过消息发送给整个集群;

思考总结

为什么Redis要设计成单线程的?

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)

可以参考:https://redis.io/topics/faq

正是由于在单线程模式的情况下已经很快了,就没有必要在使用多线程了!

redis单线程为什么效率很高

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,KV存储,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
以上几点都比较好理解,下边我们针对多路 I/O 复用模型进行简单的探讨:
(1)多路 I/O 复用模型
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。

Redis与Memcached的区别

(1)数据类型:Redis支持更加丰富的数据类型,Memcached只支持字符串模式
(2)内存使用率:Memcached的内存使用率相对于Redis的内存使用率更高,但是如果是采用Hash结构,会有一次Rehash的过程
(3)性能对比:Memcached存储大数据优于Redis;由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。
(4)Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。

1.Redis使用最佳方式是全部数据in-memory。
  2.Redis更多场景是作为Memcached的替代者来使用。
  3.当需要除key/value之外的更多数据类型支持时,使用Redis更合适。
  4.当存储的数据不能被剔除时,使用Redis更合适。

少量数据存储,高速读写访问。此类产品通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。

在项目中应用

了解服务端

在项目中的应用主要是客户端,但是对服务端也要有所了解,以便在遇到问题时能够分析问题,找出原因。

客户端的应用

实时性或一致性要求不高时,使用缓存

降级数据缓存

排行榜:排行榜如果用缓存,一般用zset数据结构

参考资料

https://www.cnblogs.com/TsingLo/p/4630157.html
http://blog.sina.com.cn/s/blog_48c95a190101694a.html

https://blog.csdn.net/tyrroo/article/details/82429801

https://redis.io/topics/faq

https://juejin.im/post/5c876854f265da2d9e179112

《redis设计与实现》 读书笔记相关推荐

  1. 读书笔记 | 墨菲定律

    1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...

  2. 读书笔记 | 墨菲定律(一)

    1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...

  3. 洛克菲勒的38封信pdf下载_《洛克菲勒写给孩子的38封信》读书笔记

    <洛克菲勒写给孩子的38封信>读书笔记 洛克菲勒写给孩子的38封信 第1封信:起点不决定终点 人人生而平等,但这种平等是权利与法律意义上的平等,与经济和文化优势无关 第2封信:运气靠策划 ...

  4. 股神大家了解多少?深度剖析股神巴菲特

    股神巴菲特是金融界里的传奇,大家是否都对股神巴菲特感兴趣呢?大家对股神了解多少?小编最近在QR社区发现了<阿尔法狗与巴菲特>,里面记载了许多股神巴菲特的人生经历,今天小编简单说一说关于股神 ...

  5. 2014巴菲特股东大会及巴菲特创业分享

     沃伦·巴菲特,这位传奇人物.在美国,巴菲特被称为"先知".在中国,他更多的被喻为"股神",巴菲特在11岁时第一次购买股票以来,白手起家缔造了一个千亿规模的 ...

  6. 《成为沃伦·巴菲特》笔记与感想

    本文首发于微信公众帐号: 一界码农(The_hard_the_luckier) 无需授权即可转载: 甚至无需保留以上版权声明-- 沃伦·巴菲特传记的纪录片 http://www.bilibili.co ...

  7. 读书笔记002:托尼.巴赞之快速阅读

    读书笔记002:托尼.巴赞之快速阅读 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<快速阅读>之后,我们就可以可以快速提高阅读速度,保持并改善理解嗯嗯管理,通过增进了解眼睛和大脑功能 ...

  8. 读书笔记001:托尼.巴赞之开动大脑

    读书笔记001:托尼.巴赞之开动大脑 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<开动大脑>之后,我们就可以对我们的大脑有更多的了解:大脑可以进行比我们预期多得多的工作:我们可以最 ...

  9. 读书笔记003:托尼.巴赞之思维导图

    读书笔记003:托尼.巴赞之思维导图 托尼.巴赞的<思维导图>一书,详细的介绍了思维发展的新概念--放射性思维:如何利用思维导图实施你的放射性思维,实现你的创造性思维,从而给出一种深刻的智 ...

  10. 产品读书《滚雪球:巴菲特和他的财富人生》

    作者简介 艾丽斯.施罗德,曾经担任世界知名投行摩根士丹利的董事总经理,因为撰写研究报告与巴菲特相识.业务上的往来使得施罗德有更多的机会与巴菲特亲密接触,她不仅是巴菲特别的忘年交,她也是第一个向巴菲特建 ...

最新文章

  1. 源码级深挖AQS队列同步器
  2. 爬虫:通过滑动或者点触验证码的方法及实现(点触+滑动)
  3. C# AutoResetEvent
  4. linux mysql删除密码忘记了_linux下忘记mysql密码的几种找回方法(推荐)
  5. 44response对象
  6. 光棍节程序员闯关秀过关全攻略
  7. burp的intruder报错Payload set 1: Invalid number settings
  8. 为什么要搭建自己的缓存管理模块?
  9. python的if循环语句_第二个是Python的循环语句,基础,使用,if,条件,判断,while,for
  10. Shiro默认拦截器
  11. 【数据科学】什么是数据科学?
  12. es6异步编程 Promise 讲解 --------各个优点缺点总结
  13. 理解JS散度(Jensen–Shannon divergence)
  14. 雷达原理---线性调频信号的MATLAB仿真
  15. 怎么进入联想电脑bios系统
  16. java导出繁体字word_利用简繁体字转换功能实现简体字文档转换为繁体字文档的方法...
  17. 边酒店V2系统v1.0.15 酒店预定 民宿客栈
  18. 运行tensorflow-datasets遇到import tensorflow.compat.v2 as tf报错ImportError: No module named tensorflow.V2
  19. taking address of temporary错误
  20. TCP 连接状态及相关命令学习

热门文章

  1. jsp连接sqlserver数据库
  2. 白领患上“网聊依赖症”
  3. ggplot绘制带误差棒、置信区间的柱状图,并调整颜色为渐变
  4. 科技“扶智”走进四川阿坝,“书路计划”让藏区孩子爱上阅读
  5. 第七回 嵌套结构逐层递进,逻辑运算先后有别
  6. 携职教育:初级会计考试明明不难,为什么通过率这么低?
  7. Spring自定义数据源配置不当引起的Mybatis拦截器Interceptors 失效/不生效
  8. 蓝桥杯 高精度加法 c++实现
  9. 使用bat命令【创建】【修改】【删除】文件
  10. 微擎的人人商城是如何对接微信支付的?