Redis复习干货,知识点满满

756 阅读25分钟

Redis特性

为什么Redis快

  1. 单线程,避免线程切换和资源竞争
  2. 非阻塞IO,使用epoll作为IO多路复用的技术实现,处理客户端请求时不会阻塞主线程
  3. 纯内存访问

Redis线程模型

Redis内部使用文件事件处理器,这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型。它采用IO多路复用机制监听多个Socket。根据socket上的事件来选择对应的事件处理器处理。 文件事件处理器结构包括

  1. 多个Socket
  2. IO多路复用程序
  3. 文件事件分派器
  4. 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) 多个Socker可能并发产生不同操作,每个操作对应不同的文件事件。但是IO多路复用程序会监听多个Socket,会将Socket产生的消息放入队列进行排队,事件分派器每次从队列中取出一个事件,把该事件交给对应事件处理器进行处理

优化网络延迟

Redis的性能瓶颈可能是网络 优化方案:

  1. 单机部署采用unix进程间通讯代替TCP
  2. multi-key的方式合并指定,减少请求数。如使用mget
  3. 使用transaction、script合并requests和responses
  4. 使用pipeline合并response

与Memcache对比

Memcache特点

  1. 多线程异步IO,可以充分利用CPU多核优势
  2. 功能简单,使用内存存放数据
  3. 缓存可以设置失效时间,数据过期会进行懒删除。超过限制内存后,除了会对过期key清理,还会用LRU策略对数据进行剔除 缺点:
  4. key不能超过250个字节,value不能超过1M,key的最大失效时间是30天,只支持K-V结构,不提供持久化和主从同步功能

与Redis对比

  1. Redis采用单线程模式处理
  2. Redis有丰富的数据结构
  3. Redis支持持久化,不仅可用作缓存,还可作为nosql数据库
  4. Redis提供主从同步机制,集群部署能力,能提供高可用服务

Redis高级用法

发布订阅功能

可用作消息队列,在消费者下线的情况下,生产的消息会丢失缺少。无法进行数据回溯

事务

Redis提供的事务只能保证串行执行命令,并且能够保证全部执行完成。但是执行命令失败时不会回滚,而是继续执行下去。不保证原子性

数据结构

Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value

String

在Redis中String称为动态字符串(检测SDS Simple Dynamic String),内部数据结构类似ArrayList,维护着一个字节数组,并且内部预分配了一定的空间,减少内存频繁分配。

内存分配机制:

  • 当字符串长度小于1M时,每次扩容都是加倍现有空间
  • 当字符串长度超过1M时,每次扩容只扩展1MB的空间 字符串的最大长度为512M

内部编码

  • int: 8个字节的长整数
  • embstr: 小于等于39字节的字符串
  • raw: 大于39个字节的字符串

数据结构:

struct SDS{
  T capacity;       //数组容量
  T len;            //实际长度
  byte flages;  //标志位,低三位表示类型
  byte[] content;   //数组内容
}

capacity 和 len两个属性都是泛型,为什么不直接用int类型?因为Redis内部有很多优化方案,为更合理的使用内存,不同长度的字符串采用不同的数据类型表示,且在创建字符串的时候 len 会和 capacity 一样大,不产生冗余的空间,所以String值可以是字符串、数字(整数、浮点数) 或者 二进制。

应用场景

  1. 缓存
  2. 计数 统计访问次数
  3. 共享session

常用命令

set   [key]  [value]   给指定key设置值(set 可覆盖老的值)
get  [key]   获取指定key 的值
del  [key]   删除指定key
exists  [key]  判断是否存在指定key
mset  [key1]  [value1]  [key2]  [value2] ...... 批量存键值对
mget  [key1]  [key2] ......   批量取key
expire [key]  [time]    给指定key 设置过期时间  单位秒
setex    [key]  [time]  [value]  等价于 set + expire 命令组合
setnx  [key]  [value]   如果key不存在则set 创建,否则返回0
incr   [key]           如果value为整数 可用 incr命令每次自增1
incrby  [key] [number]  使用incrby命令对整数值 进行增加 number

List

Redis中的List和Java的LinkedList很像,底层都是链表结构,插入和删除操作非常快时间复杂度O(1)。当数据量较少时底层的结构为一块连续的累成,称为ziplist(压缩列表),它讲所有的元素紧挨一起存储,分配的是一块连续的内存。当数据较多的时候回变成quicklist(快速与列表)。单纯链表需要维护prev和next指针需要占用较多内存。在redis3.2之后采用ziplist+链表的混合结构。简称为quicklist

内部编码

  • ziplist,当列表中的元素个数小于list-max-ziplist-entries(默认512),并且列表中每个元素值都小于list-max-ziplist-value(默认64),来减少内存使用
  • linkedlist: 当列表类型无法满足ziplist条件时,redis使用linkedlist作为列表内部实现

应用场景

  1. 消息队列:通过lpop和rpush(或rpop和lpush)实现队列功能
  2. 列表,分页数据展示lpush和lrange命令实现最新列表的功能。

常用命令

rpush  [key] [value1] [value2] ......    链表右侧插入

rpop    [key]  移除右侧列表头元素,并返回该元素

lpop   [key]    移除左侧列表头元素,并返回该元素

llen  [key]     返回该列表的元素个数

lrem [key] [count] [value]  删除列表中与value相等的元素,count是删除的个数。 count>0 表示从左侧开始查找,删除count个元素,count<0 表示从右侧开始查找,删除count个相同元素,count=0 表示删除全部相同的元素

(PS:   index 代表元素下标,index 可以为负数, index= 表示倒数第一个元素,同理 index=-2 表示倒数第二 个元素。)

lindex [key] [index]  获取list指定下标的元素 (需要遍历,时间复杂度为O(n))

lrange [key]  [start_index] [end_index]   获取list 区间内的所有元素 (时间复杂度为 O(n))

ltrim  [key]  [start_index] [end_index]   保留区间内的元素,其他元素删除(时间复杂度为 O(n))

Hash

Redis中的Hash和Java中的HashMap很相似,采用数组+链表的结构,当发生hash碰撞时将元素追加到链表上。但Redis的Hash 只能是字符串 Hash和String都可以存储用户数据。但是Hash可以对用户信息的每个字段单独存储;String存储的是序列化之后的字符串。从修改角度考虑,使用hash存储可针对某个字段进行修改,网络带宽。但是Hash的内存占用要大于String

内部编码

  1. ziplist(压缩列表),当Hash类的元素小于hash-max-ziplist-entries(默认512)同时所有值小于hash-max-ziplist-value(默认64),采用ziplist作为hash的内部实现,ziplist采用更紧凑的数据结构实现多个元素的连续储存,节约内存比hashtable更优
  2. hashtable(哈希表):当哈希类型无法满足ziplist条件时,会采用hashable作为哈希的内部实现,因为此时ziplist读写效率下降,而hashtable的读写复杂度都为O(1)

应用场景

  1. 购物车:hset [key] [field] [value] 命令, 可以实现以用户Id,商品Id为field,商品数量为value,恰好构成了购物车的3个要素。
  2. 存储对象:hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。

常用命令

hset  [key]  [field] [value]    新建字段信息

hget  [key]  [field]    获取字段信息

hdel [key] [field]  删除字段

hlen  [key]   保存的字段个数

hgetall  [key]  获取指定key 字典里的所有字段和值 (字段信息过多,会导致慢查询 慎用:亲身经历 曾经用过这个这个指令导致线上服务故障)

hmset  [key]  [field1] [value1] [field2] [value2] ......   批量创建

hincr  [key] [field]   对字段值自增

hincrby [key] [field] [number] 对字段值增加number

Set

Redis中的Set和HashSet类似,内部的键值对是无序唯一的。它的内部实现相当于一个特殊的字典,字典中所有的vlaue都是一个值null.当集合最后一个元素被移除之后,数据结构自动删除,内存被回收。

内部编码

  • intset(整形集合),当集合中的元素都是整数且个数小于set-max-intset-entries配置(默认512个),Redis会选用intset作为集合的内部实现,从而减少内存的使用。
  • hashtable,当集合无法满足intset,redsi会使用hashtable作为集合内部实现

应用场景

  1. 好友集合
  2. 随机展示。推荐商家用srandmember中随机选取几个
  3. 去重功能

常用命令

sadd  [key]  [value]  向指定key的set中添加元素

smembers [key]    获取指定key 集合中的所有元素

sismember [key] [value]   判断集合中是否存在某个value

scard [key]    获取集合的长度

spop  [key]   弹出一个元素

srem [key] [value]  删除指定元素

zset 有序集合

Zset保证了内部value的唯一性,另外可以给每个value赋值score,代表这个value的权重。内部实现用的是一种叫做跳跃列表的数据结构。

内部编码

  • ziplist:当有序列表中的元素小于zset-max-ziplist-entries(默认128),每个元素值小于zset-max-ziplist-value(默认64),采用ziplist减少内存
  • skiplist(跳跃列表),当ziplist条件不足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降

应用场景

  1. 排行榜,zset能怼数据进行动态排列
  2. 带权重的队列

常用命令

zadd [key] [score] [value] 向指定key的集合中增加元素

zrange [key] [start_index] [end_index] 获取下标范围内的元素列表,按score 排序输出

zrevrange [key] [start_index] [end_index]  获取范围内的元素列表 ,按score排序 逆序输出

zcard [key]  获取集合列表的元素个数

zrank [key] [value]  获取元素再集合中的排名

zrangebyscore [key] [score1] [score2]  输出score范围内的元素列表

zrem [key] [value]  删除元素

zscore [key] [value] 获取元素的score


高级用法

BITMAP

按bit位存储信息,记录活跃用户

HyperLogLog

利用极小的内存空间完成去重数据的统计。比如UV

Geospatial

保存地理位置,计算距离

持久化

RDB

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化的过程分为手动触发(bgsave命令)

RDB配置

  • 保存:RDB文件保存在dir配置指定目录下,文件名可通过dbfilename配置。
  • 压缩:Redis默认采用LZF算法对生成的RDB文件进行压缩处理,压缩后的文件远远小于内存大小,默认开启
  • 自动触发配置
save 900 1 
save 300 10
save 60 10000

900秒之内,如果超过1个key被修改,则发起快照保存;
300秒内,如果超过10个key被修改,则发起快照保存;
1分钟之内,如果1万个key被修改,则发起快照保存;

bgsave流程说明

  1. 执行bgsave命令,Redis父线程判断是否有其他正在执行的子进程(如RDB/AOF子进程)
  2. 父线程fork子进程,fork操作过程中会父线程会阻塞
  3. fork完成后,bgsave返回“background saving started”信息,并不再阻塞父进程
  4. 子进程创造RDB文件,根据父线程内存生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

优缺点

优点

  1. RDB采用紧密压缩的二进制文件,代表Redis在某个时间上的数据快照,非常适合备份和全量复制等场景
  2. Redis加载RDB恢复数据远远块于AOF方式 缺点
  3. 没有办法做到实时持久化/秒级持久化。bgsave命令每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高
  4. 采用二进制格式保存,存在老版本redis服务无法兼容新版RDB格式的问题

AOF

AOF(append only file)持久化;以独立日志的方式记录每次写命令,重启时再冲洗执行AOF命令文件达到回复数据的目的。

AOF配置

appendfsync yes   #默认不开启
appendfsync always     #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec   #每秒钟同步一次,该策略为AOF的缺省策略。

AOF使用流程

  1. 所有的写入命令会追加到aof_buf(缓冲区)中
  2. AOF缓冲区根据对应的策略向硬盘做同步
  3. 随着AOF文件的增大,需要定期对AOF文件进行重写
  4. 当Redis服务器重启时,可以假装AOF文件进行数据恢复

优缺点

能保存较实时的数据,存储的数据文件较大,速度慢于RDB

思考

  1. 为啥AOF采用文件协议格式? 文件协议具有良好的兼容性,开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销 文件协议具有可行性,方便直接修改和处理
  2. AOF为什么将数据追加到aof_buf中 Redis使用单线程响应命令,如果每次写AOF命令都追加到磁盘,性能完全取决于磁盘的负载。多种缓冲区同步磁盘的策略,在性能和安全性方面做出平衡。

Copy On Write

执行BGSAVE命令或者BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操作,避免不必要的内存写入操作,最大限度地节约内存。

技术实现原理

fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。

优点

  1. COW技术可减少分配和复制大量资源时带来的瞬间延时。
  2. COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。

缺点

如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。

总结

  • fork出的子进程共享父进程的物理空间,当父子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享父进程的)。
  • fork出的子进程功能实现和父进程是一样的。如果有需要,我们会用exec()把当前进程映像替换成新的进程文件,完成自己想要实现的功能。

Redis中COW

  • redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
  • 总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
  • 而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。

高可用

复制

复制流程

从节点slaveof主节点

  1. 从节点保存主节点消息
  2. 从节点建立socket套接字,主节点与其建立连接
  3. 从节点向主节点发送ping命令
  4. 权限验证
  5. 同步数据集。主节点会把持有的全部数据集发送给从节点。Redis2.8以后采用新复制命令psync进行同步。为了保证新旧版本兼容性sync仍然保留
  6. 命令持续复制

数据同步

  1. 参与复制的主从节点维护各自偏移量
  2. 复制积压缓冲区.master相应写命令时,不但会命令发送从节点,还会写入复制积压缓冲区内(先进先出队列,保存最新已复制数据),用户部分复制或复制命令失败的数据补救
  3. redis节点启动后都会分配一个字符串作为允许ID,一旦重启运行ID会改变

psync

从节点执行psync命令完成部分复制和全量复制的功能 psync {runId} offset

  • 全量复制 2. 从节点接收主节点的响应数据保存运行ID和偏移量offset 5. 主节点发送RDB文件给从节点,从节点接受RDB文件保存本地并直接作为从节点数据 6. 从节点开始接收RDB从快照到从节点接收完成期间产生的数据。这期间写数据会放入客户端缓冲区(默认64M) 7. 从节点清空自身旧数据

  • 部分复制 部分复制主要解决网络闪断或者命令丢失一次情况时,从节点向主节点要求补发丢失的命令数据,如果主节点复制积压缓冲区内存存在部分数据则直接发给从节点,若不存在则进行全力复制

哨兵

Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。

  1. 监控: 定期检测Redis数据节点、其余Sentinel节点是否可达
  2. 通知:Sentinel节点会将故障转移结果通知应用方
  3. 主节点故障转移:实现从节点晋升主节点并维护后续正确的主从关系
  4. 配置提供者:在Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,从中获得主节点信息
  5. Sentinel节点集合是有若干个Sentinel节点组成的,这样即使个别Sentinel节点不可用,整个哨兵节点任然是健壮
  6. 对于节点的故障判断是由若干个Sentinel节点共同完成,可以防止误判
  7. Sentinel节点本身就是独立的Redis节点,不储存数据,只支持部分命令

选举流程(Raft算法)

  1. 在线的Sentinel节点都有资格成为领导者,当它确认主节点客观下线(认为master下线数超过quorum)时候,会向其他Sentinel发送is-master-down-by-addr命令,要求将自己设置为领导者
  2. 其他sentinel节点收到命令,如果没有同意过其他sentinel节点的命令,则同意该请求,否者拒绝
  3. 如果节点发现自己票数大于或等于max(quorum,num(sentinels)/2+1),那么它将成为领导者
  4. 如果此过程没有选举出领导者,将进入下一次选举

故障转移

选举出的sentinel节点负责故障转移,步骤如下:

  1. 在从节点列表中选择一个节点作为新的主节点,步骤如下
  2. Sentinel领导者节点对①选择出来的从节点执行slaveof no one命令使其成为主节点
  3. Sentinel领导者会向剩余从节点发送命令,让它们成为新的主节点的从节点
  4. Sentinel节点集合会将原来的主节点更新为从节点,并保持着对其的关注,当其回复后命令它去复制新的节点

部署技巧

  1. Sentinel节点不应该部署在同一台物理机上。避免单机硬件等故障
  2. 至少3个且奇数个Sentinel。领导者选举需要至少一半加一个节点,奇数个节点可以在满足该条件基础上节省一个节点
  3. 可采用一套Sentinel 或者每个主节点配置一套Sentinel。如果监控同一业务的多个主节点集合采用一套Sentinel,否则采用另一种

集群

数据分布理论

1. 节点取余分区。

通过hash(key)%N计算出在那个节点上。节点变化时需要重新计算,可采用翻倍扩容的方式,约要迁移50%的数据

2. 一致性哈希分区

将所有的存储节点排列在首尾相接的Hash环上,每个key在计算Hash 后会顺时针找到第一个的存储节点存放。而当有节点加入或删除,仅影响该节点在Hash环上顺时针相邻的后续节点。

缺点:

  1. 加减节点时候,造成部分数据无法命中,需要手动处理或者忽略这部分数据。因此一致性哈希只试用缓存场景
  2. 当使用少量节点时,节点不是均匀的分布在哈希环上。引入虚拟节点机制,对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。使得分布更加均匀
  3. 当使用少量节点时,节点变化将大规模影响哈希环中数据映射,不适用于少量数据节点的分布式方案
  4. 普通的一致性哈希在增减节点需要增加一倍或者减少一半的节点才能保证数据负载的均衡

虚拟槽算法

所有数据都映射到一个固定的整数集合中,整数定义为哈希槽slot。用于管理数据和节点的关系。slot=CRC16(key)%16383 优点

  1. 解耦数据和节点之间关系,简化了节点扩容和收缩难度
  2. 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区的元数据
  3. 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景

集群功能限制

  1. key批量操作支持有限。mset和mget只支持相同slot值的key执行操作( Redis 4.0 仅支持相同slot,key不能保证在相同slot还是无法使用)
  2. key事务操作支持有限。只支持多key在同步给一个节点上
  3. key作为分区最小粒度,不能将大的键值对象如hash/list等映射到不同节点
  4. 集群下只支持db0操作
  5. 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构

内存管理

设置内存上线

maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存,由于内存碎片率的存在,实际消费的内存可能回避maxmemory设置更大。

目的

  1. 用于缓冲场景,当超出内存上限maxmemory时使用LUR等删除策略释放空间
  2. 防止所用内存超过服务器物理内存

动态设置内存上限

config set maxmemory 6GB

内存回收策略

删除过期键对象

  • 惰性删除:客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除命令并返回为空
  • 定时任务删除:Redis内部维护一个定时任务,默认每秒运行10秒,定时任务中删除逻辑采用自适应算法

定时删除自适应算法(默认采用慢模式运行)

  1. 定时任务在每个数据库空间随机检查20个键,当发现过期删除对应键
  2. 如果超过检查数25%的键过期,循环执行回收逻辑知道不足25%或运行超时为止,慢模式下超时时间为25毫秒
  3. 如果回收超时,Redis触发内部时间再次以快模式运行回收键任务,快模式下超时时间为1毫秒且2秒内只能运行一次(快慢模式内部删除逻辑相同,只是执行的超时时间不同)

内存溢出控制策略

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰allkeys-
  5. random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(默认):拒绝所有写请求,只响应读请求

持久化中的过期键

  • RDB 文件分为两个阶段,RDB 文件生成阶段和加载阶段。
    1. 从内存状态持久化成 RDB(文件)的时候,会对 key 进行过期检查,过期的键不会被保存到新的 RDB 文件中,因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。
    2. 如果 Redis 是主服务器运行模式的话,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键不会被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响;
    3. 如果 Redis 是从服务器运行模式的话,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。

缓存雪崩、缓存穿透、缓存击透、缓存降级等问题

缓存雪崩

缓存雪崩是指一段时间内缓存集中失效的问题。所有的查询都落到的数据库上,对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。 解决方案:

  1. 缓存时间增加随机因子,经量分散缓存过期时间
  2. 通过消息队列方式老保证不会有大量线程对数据进行一次性读写

缓存穿透

缓存穿透指缓存和数据库中都没有数据用户要查询的数据,每次都进行2次查询。若有人恶意攻击,对数据库造成压力可能会压垮数据库。 解决方案:

  1. 布隆过滤用于存储可能访问的key,布隆过滤用于大数据量的集合中判断元素是否存在(一定不存在或可能存在)
  2. 对于数据库取不到的数据,写入缓存中。缓存时间可以设置适当短一点。

缓存击透

缓存击透是指缓存中没有但是数据库中有的数据,由于并发量大,同时读取缓存没有数据而导致同时去数据库中取数据,造成数据库压力过大。 解决方案:

  1. 热点数据不过期
  2. 设置互斥锁,当获取缓存为空时候上锁,从数据库加载完毕后是否锁。若其他线程获取锁,睡眠50ms后重新尝试。这里的锁需要考虑java并发包和集群环境下的分布式锁

缓存降级

缓存降级指当访问量突然剧增,服务出现问题或者非核心业务影响到核心业务性能时,需要包装服务还是可用。系统可用根据一些关键数据进行降级,来保证核心业务的可用。例如redis中适当删除非关键缓存数据。

参考文章

付磊、张益军《Redis开发与运维》 Redis 性能问题分析 Redis数据结构及对应使用场景,看一次就整明白得了 Redis面试全攻略,读完这个就可以和面试官大战几个回合了 MemCache详细解读 COW奶牛!Copy On Write机制了解一下 Redis常见面试题