「持续更新中,欢迎关注...」redis
1. 什么是Redis
Redis 是互联网技术领域使用最为普遍的存储中间件,它是Remote Dictionary Service的首字母缩写,也就是远程字典服务。Redis 以其超高的性能、完美的文档、简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评。对 Redis 的了解和应用实践已成为当下中高级后端开发者绕不开的必备技能。算法
2. Redis能够作什么
- 数据缓存
- 消息队列
- 分布式锁
- 会话缓存
- 时效性数据
- 访问频率
- 计数器
- 社交列表
- 记录用户断定信息
- 交集、并集和差集
- 热门列表与排行榜
- 最新动态
- 大数据去重
3. Redis的优势
- 速度快:内存存储,查找和操做的时间复杂度O(1)。
- 支持丰富的数据类型:提供了String、List、Hash、Set、Sorted Set 5种基础数据结构,并扩展了Bitmap、HyperLogLog、GEO等高级数据结构。
- 丰富的特性:订阅发布Pub/Sub、Key过时策略、事务、计数、Stream等。
- 持久化存储:提供了RDB和AOF两种数据的持久化存储方案,解决宕机数据丢失的问题。
- 高可用:提供Redis Sentinel高可用方案和Redis Cluster集群方案。
4. Redis和Memcached区别
- 数据结构对比:Redis支持更复杂的数据结构,能支持更丰富的数据操做;Memcached 仅提供简单的字符串。
- 性能对比:Redis 只使用单核(Redis 6开始支持多核),而 Memcached 可使用多核,因此平均每个核上 Redis在存储小数据时比 Memcached 性能更高。
- 持久化对比:Redis 支持数据持久化和数据恢复,容许单点故障,可是同时也会付出性能的代价。Memcached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。
- 集群对比:Redis3.x+ 版本中,官方便能支持 Cluster 集群模式。Memcached 没有原生的集群模式,须要依靠客户端来实现往集群中分片写入数据。Memcached的分布式不是在服务器端实现的,而是在客户端应用中实现的,即经过内置算法制定目标数据的节点。
- 内存利用率对比:简单的 Key-Value 存储的话,Memcached 的内存利用率更高,可使用相似内存池。
若是 Redis 采用 hash 结构来作 key-value 存储,因为其组合式的压缩, 其内存利用率会高于 Memcached 。数据库
- 内存管理对比:Redis 采用的是包装的 malloc/free ,相较于 Memcached 的内存管理方法 tcmalloc / jmalloc 来讲,要简单不少。
- 网络IO模型对比:Memcached 是多线程,非阻塞 IO 复用的网络模型,原型上接近 Nignx 。Redis 使用单线程的 IO 复用模型,本身封装了一个简单的 AeEvent 事件处理框架,主要实现了 epoll, kqueue 和 select ,更接近 Apache 早期的模式。
5. 为何 Redis 这么快?
- C语言实现。
- 纯内存操做。
- 核心是基于非阻塞的IO多路复用机制。
- 单线程反而避免了多线程的频繁上下文切换问题。Redis 利用队列技术,将并发访问变为串行访问,消除了传统数据库串行控制的开销。第一,单线程能够简化数据结构和算法的实现。第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来讲,锁和线程切换一般是性能杀手。
- Redis 使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表对短数据进行压缩存储;再如跳表使用有序的数据结构加快读取的速度。
6. Redis线程模型
关键字:非阻塞 IO、多路复用。后端
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,因此 redis 才叫作单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:数组
- 多个 socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器(链接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不一样的操做,每一个操做对应不一样的文件事件,可是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。缓存

- 客户端 socket01 向 redis 的 server socket 请求创建链接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给链接应答处理器。链接应答处理器会建立一个能与客户端通讯的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。
- 假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,因为前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,所以事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在本身内存中完成 key value 的设置。操做完成后,它会将 socket01 的 AE_WRITABLE 事件与令回复处理器关联。
- 若是此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,一样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操做的一个结果,好比 ok,以后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。
7. 如何提升Redis多核 CPU 的利用率
能够在同一个服务器部署多个 Redis 的实例,并把他们看成不一样的服务器来使用,在某些时候,不管如何一个服务器是不够的,因此,若是想使用多个 CPU ,能够考虑一下分区。
升级到Redis 6,能够利用多核CPU。服务器
8. Redis数据结构及应用场景
Redis 全部的数据结构都是以惟一的 key 字符串做为名称,而后经过这个惟一 key 值来获取相应的 value 数据。不一样类型的数据结构的差别就在于 value 的结构不同。网络
8.1 Redis 5种基础数据结构:
- string(字符串):string表示的是一个可变的字节数组,Redis的字符串是动态字符串,是能够修改的字符串,内部结构实现上相似于Java的ArrayList,采用预分配冗余空间的方式来减小内存的频繁分配。当字符串长度小于1M时,扩容都是加倍现有的空间,若是超过1M,扩容时一次只会多扩1M的空间。须要注意的是字符串最大长度为512M。
- list (列表):list存储结构用的是双向链表而不是数组,随机定位性能较弱( O(n)),首尾插入删除性能较优(O(1))。Redis底层存储是称之为快速链表quicklist的一个结构。首先在列表元素较少的状况下会使用一块连续的内存存储,这个结构是ziplist,也便是压缩列表。它将全部的元素紧挨着一块儿存储,分配的是一块连续的内存。当数据量比较多的时候才会改为quicklist。由于普通的链表须要的附加指针空间太大,会比较浪费空间。好比这个列表里存的只是int类型的数据,结构上还须要两个额外的指针prev和next。因此Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既知足了快速的插入删除性能,又不会出现太大的空间冗余。

- hash (哈希):哈希等价于Java语言的HashMap,在实现结构上它使用二维结构,第一维是数组,第二维是链表,hash的内容key和value存放在链表中,数组里存放的是链表的头指针。经过key查找元素时,先计算key的hashcode,而后用hashcode对数组的长度进行取模定位到链表的表头,再对链表进行遍历获取到相应的value值,链表的做用就是用来将产生了「hash碰撞」的元素串起来。

- set (集合):set至关于 Java 语言里面的 HashSet,使用hash结构,全部的value都指向同一个内部值。
当集合中最后一个元素移除以后,数据结构自动删除,内存被回收。数据结构
- zset(Sorted Set) (有序集合):zset底层实现使用了两个数据结构,第一个是hash,第二个是跳跃列表,hash的做用就是关联元素value和权重score,保障元素value的惟一性,能够经过元素value找到相应的score值。跳跃列表的目的在于给元素value排序,根据score的范围获取元素列表。
zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
zset 能够用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。咱们能够对粉丝列表按关注时间进行排序。多线程
8.2 Redis 4种特殊数据结构:
- HyperLogLog
- Bitmaps
- GEO
- Stream
Redis Module
- BloomFilter
- RedisSearch
- Redis-ML
- JSON
Redis 5.0新增
9. Redis数据“过时”策略
在Redis中,同时使用了3种过时策略:
- 被动删除:当读/写一个已通过期的 key 时,会触发惰性删除策略,直接删除掉这个过时 key 。
- 主动删除:因为惰性删除策略没法保证冷数据被及时删掉,因此 Redis 会按期主动淘汰一批已过时的 key 。
- 当前已用内存超过 maxmemory 限定时,触发主动清理策略,即 「数据“淘汰”策略」 。
10. Redis数据“淘汰”策略
Redis 在生产环境中,采用配置参数 maxmemory 的方式来限制内存大小。当 Redis 内存超出物理内存限制时,内存数据就会与磁盘产生频繁交换,使 Redis 性能急剧降低。所以如何淘汰无用数据释放空间,存储新数据变得尤其重要。
10.1 四种淘汰机制:
- LRU(Least recently used,最近最少使用):
- 根据数据的历史访问记录来进行淘汰数据,其核心思想是“若是数据最近被访问过,那么未来被访问的概率也更高”。
- 在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据server.unixtime 计算出来进行排序的,而后选择最近使用时间最久的数据进行删除。每个 redis 对象都会设置相应的 lru。每一次访问数据,会更新对应lru。
- LRU淘汰策略并非面向全部最久未使用的键值对,而只是随机挑选的几个键值对。
- TTL:
Redis 数据集数据结构中保存了键值对过时时间的表,TTL 数据淘汰机制中会先从过时时间的表中随机挑选几个键值对,取出其中 ttl 最大的键值对淘汰。TTL淘汰策略并非面向全部过时时间的表中最快过时的键值对,而只是随机挑选的几个键值对。
- RANDOM:
在随机淘汰的场景下获取待删除的键值对,随机找hash桶再次hash指定位置的dictEntry。
- LFU(Least Frequently Used,最久未使用)
- 根据数据的历史访问记录来进行淘汰数据,其核心思想是“若是一个数据在最近一段时间内使用次数不多,那么在未来一段时间内被使用的可能性也很小”。
10.2 8种淘汰策略及应用场景
Redis 提供了 8 种数据淘汰策略,其中volatile-lfu、allkeys-lfu 2种是Redis 4.0新增。
- volatile-lru
- 从已设置过时时间的数据集中挑选最近最少使用的数据淘汰。
- redis并非保证取得全部数据集中最近最少使用的键值对,而只是随机挑选的几个键值对中的,当内存达到限制的时候没法写入非过时时间的数据集。
- 没有设置过时时间的key不会被淘汰,这样就能够在增长内存空间的同时保证须要持久化的数据不会丢失。
- volatile-ttl
- 从已设置过时时间的数据集中挑选将要过时的数据淘汰。
- redis 并非保证取得全部数据集中最近将要过时的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候没法写入非过时时间的数据集。
- ttl值越大越优先被淘汰。
- volatile-random
- 从已设置过时时间的数据集中任意选择数据淘汰。
- 当内存达到限制没法写入非过时时间的数据集时。
- allkeys-lru
- 从数据集中挑选最近最少使用的数据淘汰。
- 当内存达到限制的时候,对全部数据集挑选最近最少使用的数据淘汰,可写入新的数据集。
- 适用场景:若是咱们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者咱们不太清楚咱们应用的缓存访问分布情况,咱们能够选择allkeys-lru策略。
- allkeys-random
- 从数据集中任意选择数据淘汰。
- 当内存达到限制的时候,对全部数据集挑选随机淘汰,可写入新的数据集。
- 适用场景:若是咱们的应用对于缓存key的访问几率相等,则可使用这个策略。
- no-enviction(默认策略)
- 当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,全部引发申请内存的命令会报错。
- volatile-lfu
- allkeys-lfu
10.3 Redis 回收进程如何工做的?
- 一个客户端运行了新的写命令,发起须要更多内存的申请。
- Redis 检查内存使用状况,若是大于 maxmemory 的限制, 则根据设定好的策略进行回收。
- Redis 执行新命令。