Redis是什么,Redis为什么这么快?

简介

Redis是一款使用C语言编写、可基于内存亦可持久化的日志型、Key-Value型开源数据库。它可以用作:数据库、缓存和消息中间件。

数据结构

  • String:缓存、计数器、分布式锁等。
  • List:链表、队列、微博关注人时间轴列表等。
  • Hash:用户信息、Hash 表等。
  • Set:去重、赞、踩、共同好友等。
  • Zset:访问量排行榜、点击量排行榜等。
  • 范围查询,Bitmaps,Hyperloglogs 和地理空间(Geospatial)索引半径查询...

Redis为什么快

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,官方提供的数据是可以达到 「100000+」 的QPS(每秒内查询次数)

分析原因

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的,如SDS,跳跃表等等(后续文章会单独介绍)
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
  4. 使用多路I/O复用模型,非阻塞IO;
  5. Redis为什么如此高效_redis

  6. 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

持久化

快照rdb冷备

Redis为什么如此高效_数据_02

rdb

  • 一次全量备份;
  • 内存数据的二进制序列化形式,格式紧凑;
  • 使用操作系统多进程cow(copy on write)机制来实现快照持久化;调用glibc的函数fork产生一个子进程,快照持久化交给子进程处理,子进程产生时,和父进程共享内存里面的代码段和数据段;

aof日志热备


Redis为什么如此高效_缓存_03


  • 连续增量备份;
  • 内存数据修改的指令记录文本,需要定期瘦身,进行aof重写;
  • redis的bgrewriteaof指令对aof进行瘦身,原理是开辟一个子进程对内存进行遍历,转换成一系列指令,序列化到一个新的aof日志文件中,序列化完毕后将这期间增量的日志追加到新aof文件中,追加完毕后替换旧的aof文件,瘦身完成;
  • fsync:日志文件的写操作,先将内容写到内核为文件描述符分配的一个内存缓冲中,然后内核异步刷新脏数据到磁盘;

过期策略和内存淘汰机制

当我们对某些 key 设置了 expire 时,数据到了时间会自动删除。如果一个键过期了,它会在什么时候删除呢?

三种删除策略:

  1. 定时删除:在这是键的过期时间的同时,创建一个定时器 Timer,让定时器在键过期时间来临时立即执行对过期键的删除。
  2. 惰性删除:键过期后不管,每次读取该键时,判断该键是否过期,如果过期删除该键返回空。
  3. 定期删除:每隔一段时间对数据库中的过期键进行一次检查。

「优劣总结」

「定时删除」:对内存友好,对 CPU 不友好。如果过期删除的键比较多的时候,删除键这一行为会占用相当一部分 CPU 性能,会对 Redis 的吞吐量造成一定影响。

「惰性删除」:对 CPU 友好,内存不友好。如果很多键过期了,但在将来很长一段时间内没有很多客户端访问该键导致过期键不会被删除,占用大量内存空间。

「定期删除」:是定时删除和惰性删除的一种折中。每隔一段时间执行一次删除过期键的操作,并且限制删除操作执行的时长和频率。

具体的操作如下:

  • Redis 会将每一个设置了 expire 的键存储在一个独立的字典中,以后会定时遍历这个字典来删除过期的 key。除了定时遍历外,它还会使用惰性删除策略来删除过期的 key。
  • Redis 默认每秒进行十次过期扫描,过期扫描不会扫描所有过期字典中的 key,而是采用了一种简单的贪心策略。
  • 从过期字典中随机选择 20 个 key;删除这 20 个 key 中已过期的 key;如果过期 key 比例超过 1/4,那就重复步骤 1。
  • 同时,为了保证在过期扫描期间不会出现过度循环,导致线程卡死,算法还增加了扫描时间上限,默认不会超过 25ms。

内存淘汰机制

Redis在使用内存达到某个阈值(通过maxmemory配置)的时候,就会触发内存淘汰机制,选取一些key来删除。内存淘汰有许多策略,下面分别介绍这几种不同的策略。

# maxmemory <bytes> 配置内存阈值
# maxmemory-policy noeviction
  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

如何选取合适的策略?比较推荐的是两种lru策略。根据自己的业务需求。如果你使用Redis只是作为缓存,不作为DB持久化,那推荐选择allkeys-lru;如果你使用Redis同时用于缓存和数据持久化,那推荐选择volatile-lru。

LRU是Least Recently Used的缩写,即最近最少使用。LRU源于操作系统的一种页面置换算法,选择最近最久未使用的页面予以淘汰。在Redis里,就是选择最近最久未使用的key进行删除。

缓存常见问题

缓存穿透

「现象」

请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去

「问题」

拿一个不存在的id 去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉。

「解决」

为这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null

缓存击穿

「现象」

在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。

「问题」

造成某一时刻数据库请求量过大,压力剧增。

「解决」

上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存

缓存雪崩

「现象」

当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。

「问题」

DB 抗不住,挂掉。

「解决」

在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效

setRedis(Key,value,time + Math.random() * 10000);

高可用

Redis的几种常见高可用使用方式包括:. Redis多副本(主从);. Redis Sentinel(哨兵);. Redis Cluster;

sentinel着眼于高可用,Cluster提高并发量。

Redis多副本(主从)

Redis为什么如此高效_数据_04

master-slave

Redis多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。

当master 宕机,需要手动将一台slave使用slaveof no one提升为master

Redis Sentinel(哨兵)

Redis为什么如此高效_缓存_05

sentinel

为了解决Redis的主从复制的不支持高可用性能,Redis实现了Sentinel哨兵机制解决方案

Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现可以互相监视、故障发现、故障自动转移、配置中心和客户端通知,自动将下线的主服务器属下的某个从服务器升级为新的主服务器。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。

Redis为什么如此高效_缓存_06

工作原理

默认情况下,每个 Sentinel 节点会以 每秒一次 的频率对 Redis 节点和 其它 的 Sentinel 节点发送 PING 命令,并通过节点的 回复 来判断节点是否在线。

「主观下线」

适用于所有 主节点 和 从节点。如果在 down-after-milliseconds 毫秒内,Sentinel 没有收到 目标节点 的有效回复,则会判定 该节点 为 主观下线。

「客观下线」

只适用于 主节点。如果 主节点 出现故障,Sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的 状态判断。如果超过个数的节点判定 主节点 不可达,则该 Sentinel 节点会判断 主节点 为 客观下线

Redis Cluster(集群)

Redis为什么如此高效_数据_07

redis-cluster

Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。

Redis Cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念,采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所映射的键值数据。

「哈希槽的好处」

  1. 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
  2. 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
  3. 在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。

小结

本文主要介绍了redis为什么快的原因,常见redis缓存问题,持久化、缓存机制和高可用,后续会针对数据结构、布隆过滤器等等做详细介绍。

参考文章

​https://cloud.tencent.com/developer/article/1345316 ​

https://zhuanlan.zhihu.com/p/57089960

​https://hogwartsrico.github.io/2020/06/24/Redis-and-Multiplexing/ ​

https://developer.aliyun.com/article/626532