目录

Redis究竟是什么

Redis为什么能够做到这么快

Redis持久化机制

Redis如何实现过期的key的删除

Redis数据类型及应用场景

Redis的缓存穿透如何解决

   什么是缓存穿透?

解决方案:

布隆过滤器

Redis如何解决缓存雪崩

什么是缓存雪崩

措施

Redis分布式锁的实现原理

Redis集群方案

具体场景

Redis集群主从同步原理

Redis缓存一致性解决方案

Redis内存淘汰策略


Redis究竟是什么

        Redis是一个开源的高性能键值存储系统,也被称为数据结构服务器。它不仅仅是一个传统意义上的数据库,而是一个功能强大且多用途的存储系统。

        虽然Redis可以像数据库那样存储和检索数据,但它与传统关系型数据库(如MySQL)有很多区别:

  1. 数据模型:Redis主要使用键值对来存储数据,其中键是唯一标识符,值可以是字符串、哈希、列表、集合、有序集合等不同的数据结构。这使得Redis更加灵活,可以以不同的方式组织和访问数据。

  2. 内存数据库:Redis将数据存储在内存中,这使得它能够提供非常快速的读写性能。与之相比,许多关系型数据库使用磁盘进行持久化存储。尽管Redis支持将数据持久化到磁盘上,但其主要优势在于内存存储。

  3. 单线程架构:Redis采用单线程的事件驱动模型,这使得它能够处理高并发请求。它通过异步I/O和非阻塞操作来实现高性能。

  4. 数据库功能的扩展:除了基本的存储和检索操作,Redis还提供了许多附加功能,如发布/订阅、事务、Lua脚本、过期键管理等。这些功能使得Redis不仅可以用于缓存和临时数据存储,还可以用作消息队列、计数器、实时排行榜等应用场景。

        我们可以将Redis看作是一个多用途的、高效的数据结构服务器,广泛应用于缓存、会话存储、实时分析、消息传递等领域。

Redis为什么能够做到这么快

(1)完全基于内存操作

(2)数据结构简单,对数据操作也相对简单

(3)Redis执行命令是单线程的,避免了上下文切换所带来的性能开销,也不用考虑锁的问题

(4)采用了非阻塞的IO多路复用技术,使用单线程来处理并发的连接;内部采用的epoll+自己实现的事件分离器

        其实Redis并不是完全多线程的,在核心的网络模型中采用多线程用于处理并发连接,但是数据的操作都是单线程的。

        Redis坚持单线程是因为Redis的性能瓶颈是网络延迟而不是CPU,因此多线程不会带来明显的性能提升。

Redis持久化机制

(1)快照持久化RDB

        Redis默认的持久化机制,通过父进程fork一个子进程,子进程将Redis的数据快照写入一个临时文件,等待持久化完毕之后替换上一次的rdb文件。整个过程主进程不进行任何的IO操作。持久化策略可以通过save配置单位时间内执行多少次操作触发持久化。所以RDB的优点是保证Redis性能最大化,恢复数据速度较快,缺点是可能丢失两次快照之间的数据

(2)追加持久化AOF

        以日志的形式记录每一次的写入和删除操作,策略有每秒同步、每次操作同步、不同步,优点是数据完整性高,缺点是运行效率低、恢复时间长

Redis如何实现过期的key的删除

采用了定期过期+惰性过期

定期删除:Redis每隔一段时间从那些设置了过期时间的key的集合里面,随机抽取一些key,查看是否过期,如果过期就将其删除。

惰性删除:Redis在key被访问的时候检查key是否过期(先返回此次的结果,然后立即删除),如果过期则删除

Redis数据类型及应用场景

String:可以用来缓存JSON信息,可以用incr命令实现自增或自减的计数器。

Hash:与String一样可以保存json信息

List:可以用来做消息队列,list的pop是原子性操作能一定程度上保证线程安全

Set:可以实现去重,比如一个用户只能参加一次活动

ZSet:有序的,可以实现排行榜

Redis的缓存穿透如何解决

使用Redis这样的缓存系统时,经常遇到的问题就是——缓存穿透

        首先来理解一下缓存的作用。缓存是一种将数据存储到内存的技术,它可以大大提高读取数据的速度。当我们需要获取某个数据的时候,会首先尝试从缓存中读取,如果缓存中存在这个数据,那么我们可以迅速的获取到结果。如果缓存中没有这个数据,则从数据库中读取,然后将结果存入缓存当中,以便下次使用。

   什么是缓存穿透?

        当一个请求查询一个不存在缓存和数据库的数据时(就是根本就是一条假数据),这个请求每次都会穿过缓存,去数据库中查找全部,然后返回没有查询到的结果如果并发请求数量很大,数据库的负载会非常高,响应速度也会变得很慢。

        举个通俗的例子来说明,假设你是一个超市的收银员,有个顾客每次来买东西都问你要一个不存在的商品编码,你翻遍货架和数据库都找不到这个商品,最后只能告诉顾客没有这个商品。如果每次都有不同的顾客来问同样的问题,肯定就会觉得很无奈和烦躁。

解决方案:

  1. 在接口上做基础的校验,比如id<=0就拦截
  2. 缓存空对象:找不到的数据也缓存起来,并设置过期时间,可能会造成短期不一致
  3. 布隆过滤器:在客户端和缓存直接加一层过滤器,拦截掉不存在的数据请求(用于快速判断请求的数据是否存在。如果布隆过滤器认定数据不存在,就可以避免对数据库进行不必要的查询)

布隆过滤器

        布隆过滤器使用哈希函数和位数组来进行判定。它的基本原理是将元素通过多个哈希函数映射为位数组中的位置,并将这些位置标记为已存在。当查询一个元素时,布隆过滤器会将元素经过相同的哈希函数映射到位数组中的位置,并检查这些位置是否都已被标记。如果有任何一个位置未被标记,那么该元素就被判定为不存在于布隆过滤器中;否则,该元素被判定为可能存在于布隆过滤器中。

        然而,布隆过滤器存在一定的误判率,即偶尔会将不存在的元素误判为存在。这是因为多个元素可能经过哈希函数映射到位数组时产生冲突,导致相同的位置被标记。误判率主要受到布隆过滤器的容量(位数组大小)和哈希函数数量的影响,以及元素插入数量和哈希函数的选择有关。较低的误判率需要更大的位数组和更多的哈希函数,但也会增加存储空间和查询时间的开销。

        当有数据插入时,布隆过滤器会通过多个哈希函数计算出对应的位数组位置,并将这些位置的值设置为1,表示该位置已被标记。这样做的目的是在插入数据时,确保每一条数据都会在位数组上进行初始化标记

Redis如何解决缓存雪崩

什么是缓存雪崩

        要理解缓存雪崩的原因,需要明白缓存的失效机制。通常,在设置缓存的时候会给每个缓存设置一个过期时间过了这个过期时间缓存就会自动失效。当失效时间接近的时候,如果再对该条数据访问系统回去从数据库中重新加载数据,并更新缓存。然而,如果有大量缓存数据在同一时间失效,而且系统无法即使处理这些失效请求,就会导致所有请求全部打到访问数据库上面,造成数据库压力的瞬间激增,最终导致系统崩溃。

        举例:假设有一个电商网站,商品信息被存在了Redis当中,每个商品的缓存时间是1小时。然后在某一时刻,由于某种原因,大量商品缓存同时失效。此时,在下一个小时内的所有请求都会直接访问数据库来获取商品信息,导致数据库瞬间承受巨大的压力。

措施

1、针对缓存数据设置不同的过期时间,是缓存失效的时间点分散,避免集中失效

2、设置热点数据永不过期,保证热门数据的一直可用

3、使用集群部署或主从复制等机制,增加缓存层的容错和高可用性。

4、在缓存失效的时候,通过加锁操作只允许一个线程去访问数据库,其他线程等待获取缓存。

5、使用限流和降级策略,当数据库压力过大的时候,暂时拒绝一部分请求或返回默认值。

Redis分布式锁的实现原

下面是Redis分布式锁的主要实现原理:

  1. 获取锁:当一个客户端想获取锁时,它会通过执行SET命令将一个特定的键(作为锁的标识)设置到Redis中,并指定一个过期时间。例如,可以使用SETNX(SET if Not eXists)命令来设置锁,如果该键不存在才会成功设置。

  2. 锁的所有权:只能由设置锁的客户端持有,在设置锁时会返回一个唯一标识符(例如UUID)作为锁的所有者标记。

  3. 防止死锁:为了防止某个客户端获取锁后发生故障而导致无法释放锁,可以为锁设置一个合适的过期时间,确保即使持有锁的客户端异常退出或崩溃,锁最终也会过期自动释放。

  4. 释放锁:客户端在完成了对共享资源的操作后,可以通过执行DEL命令来删除对应的锁键,将锁释放。此时其他客户端就可以尝试获取该锁。

需要注意的是,为了确保获取锁和释放锁的原子性操作,可以使用Redis的Lua脚本执行命令,以保证在执行期间不会被其他命令插入。

使用Redis分布式锁时需要注意以下几点:

  • 确保锁的标识符在每个客户端是唯一且具有足够的随机性,可以使用UUID等方式生成。
  • 设置适当的过期时间,以避免长时间持有锁而导致其他客户端无法获取到锁。
  • 处理获取锁失败的情况,可以使用重试机制来尝试重新获取锁,或者设置超时时间避免无限等待。

Redis集群方案

主从模式:一个master节点,多个slave节点,master节点宕机后slave自动变成主节点

哨兵模式:在主从集群基础上添加添加哨兵节点或哨兵集群,用于监控master节点健康状态,通过投票机制选择slave为成为主节点

分片集群:主从模式和哨兵模式解决了并发读的问题,但没有解决并发写的问题,因此有了分片集群。分片集群有多个master节点并且不同master保存着不同的数据,master直接通过ping互相监测其他master的健康状态。

        在分片集群中,客户端可以向任意一个节点发送请求,该节点将根据请求的数据进行哈希计算,并将请求转发到正确的主节点。为了实现这一目的,集群将数据划分为多个插槽,每个插槽范围是0-16384。每个主节点负责管理一部分插槽,并维护与之相关的数据。

        当客户端发送写入操作时,集群会确定该操作属于哪个插槽范围,然后将请求转发到负责该插槽的主节点,由主节点负责处理写入操作并更新对应的数据。        

具体场景

        假设我们有一个在线电商平台,每天有成千上万的用户在该平台上进行商品搜索、浏览和购买操作。为了支持这样的高并发访问,我们决定采用分片集群来处理数据存储和查询的需求。

        首先,我们将数据根据某个字段(比如用户ID、商品ID等)的哈希值进行分片,生成对应的插槽范围。假设我们选择将用户信息按用户ID进行分片,并设置了4个主节点(master)。

        接下来,我们将每个主节点分配一定范围的插槽来管理。例如主节点1负责插槽范围0-4095,主节点2负责插槽范围4096-8191,以此类推。每个主节点都会维护自己所负责的插槽和对应的数据。

        当用户发送一个查询请求时,客户端会将该请求发送到任意一个主节点。假设这次请求涉及到用户ID为12345的用户信息。此时,集群会通过哈希算法计算出用户ID的哈希值,并确定该查询属于哪个插槽范围。

        假设哈希值为7890,根据插槽范围的规则,这个查询属于主节点2负责的插槽范围。因此,当前的主节点将会把这个查询请求转发给主节点2。主节点2接收到请求后,从自己负责的插槽中找到对应的数据,并将查询结果返回给客户端。

Redis集群主从同步原理

        主从同步第一次是全量同步:slave第一次请求master节点会根据replid判断是否是第一次同步,是的话master会生成RDB发送给slave

        后续为增量服务:在发送RDB期间,会产生一个缓存区间记录发送RDB期间产生的新的命令,slave节点在加载完之后,会持续缓存区间内的数据

第一次全量同步,也称为初始同步。当从节点第一次连接到主节点时,它会请求主节点进行全量数据复制。主节点会生成一个RDB(Redis Database)文件,包含当前所有的数据,并将该文件发送给从节点。从节点接收到RDB文件后,将其加载到内存中,并与主节点保持同步。

后续的同步则是增量同步。在全量同步过程中,如果主节点有新的写入操作,这些操作将被记录在缓存区间中,而不会立即发送给从节点。当全量同步完成后,主节点会将缓存区间中的变更命令发送给从节点。此时,从节点会持续地读取并执行这些变更命令,从而将自身的数据与主节点保持同步。

通过全量同步和增量同步的方式,主从同步能够确保从节点始终与主节点保持最新的数据一致性。全量同步用于确保从节点能够获取到完整的数据,增量同步则用于实时更新从节点的数据,以保持与主节点的一致性。

这种主从同步机制可以提高系统的可用性和性能。当主节点出现故障时,从节点可以顶替主节点的角色,继续提供服务。同时,从节点还可以分担主节点的读取负载,提高系统的并发处理能力。

Redis缓存一致性解决方案

        Redis缓存一致性解决方案主要思考的是删除缓存和更新数据库的先后顺序。

  1. 先删除缓存后更新数据库(Cache-Aside策略):

    • 当需要读取数据时,首先从缓存中查找,如果缓存中存在,则直接返回缓存中的数据。
    • 如果缓存中不存在,则从数据库中读取数据,并将读取到的数据写入缓存,同时再返回给调用方使用。
    • 当进行数据更新操作时,先更新数据库中的数据,然后再删除缓存中对应的数据。
    • 下次读取该数据时,会发现缓存已经不存在,再次按照上述步骤重新加载数据到缓存中。
  2. 先更新数据库后删除缓存(Write-Through Cache策略):

    • 当进行数据更新操作时,先更新数据库中的数据,确保数据库中的数据是最新的。
    • 然后,直接更新缓存中对应的键值对,使得缓存中的数据与数据库中的数据保持一致。
    • 下次读取该数据时,由于缓存中已经包含了最新的数据,可以直接从缓存中获取,避免了再次访问数据库的开销。

Redis内存淘汰策略

当 Redis 的内存使用达到配置的最大内存限制时,就需要启用内存淘汰策略。

Redis 内存淘汰的目的是为了在内存不足时,从已有的数据集中选择一些键值对进行删除,以释放一定的内存空间。内存淘汰可以通过一定的策略来选择要删除的键值对,以满足特定的需求和优化目标。

Redis 提供了多种内存淘汰策略:

  1. noeviction(默认值):当 Redis 内存使用达到最大限制时,对新的写入操作进行阻塞,并返回错误信息,告知内存不足。

  2. allkeys-lru:使用 LRU(Least Recently Used,最近最少使用)算法,在所有键中选择最近最少使用的键进行淘汰。

  3. allkeys-random:随机选择要淘汰的键。

  4. volatile-lru:使用 LRU 算法,在设置了过期时间的键中选择最近最少使用的键进行淘汰。

  5. volatile-random:在设置了过期时间的键中随机选择要淘汰的键。

通俗来说就是三条:

(1)淘汰最久没有使用过的

(2)淘汰一段时间内最少使用的

(3)淘汰快要过期的

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐