Redis 知识点

Redis 数据结构:

  1. 字符串(String)

    • 操作:
      • SET key value:设置指定key的值。
      • GET key:获取指定key的值。
      • INCR key:原子性递增key的整数值。
      • DECR key:原子性递减key的整数值。
      • APPEND key value:追加值到已存在key对应的字符串末尾。
  2. 列表(List)

    • 操作:
      • RPUSH key value [value ...]:将一个或多个值推送到列表尾部。
      • LPUSH key value [value ...]:将一个或多个值推送到列表头部。
      • LPOP key:从列表头部弹出(移除并返回)一个元素。
      • RPOP key:从列表尾部弹出(移除并返回)一个元素。
      • LRANGE key start stop:获取列表指定范围内的元素。
  3. 哈希表(Hash)

    • 操作:
      • HSET key field value:设置哈希表key中指定字段的值。
      • HGET key field:获取哈希表key中指定字段的值。
      • HGETALL key:获取哈希表key中所有字段及其值。
      • HDEL key field [field ...]:删除哈希表key中一个或多个指定字段。
  4. 集合(Set)

    • 操作:
      • SADD key member [member ...]:向集合添加一个或多个成员。
      • SISMEMBER key member:判断成员是否存在于集合中。
      • SMEMBERS key:获取集合中的所有成员。
      • SREM key member [member ...]:从集合中移除一个或多个成员。
      • SRANDMEMBER key [count]:随机返回集合中的一个或多个成员。
  5. 有序集合(Sorted Set)

    • 操作:
      • ZADD key score member [score member ...]:向有序集合添加一个或多个成员,按score排序。
      • ZRANGE key start stop [WITHSCORES]:返回有序集合中指定区间内的成员,按score从小到大排序。
      • ZREM key member [member ...]:从有序集合中删除一个或多个成员。
      • ZSCORE key member:返回有序集合中指定成员的score值。

一、Redis 特点

  1. 高性能
    • Redis 是基于内存存储的数据库,这意味着它的数据读写速度极快,因为数据直接在内存中操作,避免了磁盘 I/O 的瓶颈。
    • Redis 采用 C 语言编写,能够充分利用 CPU 和内存资源,执行效率高。
    • Redis 支持单线程模型,网络请求模块在一个线程中顺序处理,避免了多线程环境下的锁竞争和上下文切换带来的性能损失。
  2. 数据持久化
    • Redis 支持两种持久化方式:RDB(Redis Database)和 AOF(Append Only File),可以将内存中的数据定期或实时地保存到磁盘上,防止数据丢失。
  3. 丰富的数据结构
    • Redis 不仅支持简单的 Key-Value 存储,还支持复杂的数据结构,如字符串(Strings)、哈希表(Hashes)、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)等,这使得 Redis 成为一种功能强大的数据结构服务器。
  4. 多客户端支持
    • Redis 提供简洁的 TCP 通信协议,支持多种编程语言的客户端,如 Java、Python、PHP、C、C++、Node.js 等,方便开发者使用。
  5. 事务支持
    • Redis 提供简单的事务处理功能,尽管事务并不保证严格的ACID特性,但在执行一组命令时,它可以保证这些命令以原子方式执行。
  6. 集群与分片
    • Redis 可以通过主从复制(Master-Slave replication)实现数据备份和读负载均衡。
    • Redis Cluster 提供了原生的分布式解决方案,能够将数据分布在多个节点上,实现数据分片,并具有一定程度的容错和自动故障转移功能。
  7. 简单稳定
    • Redis 的源代码相对较小,结构清晰,易于理解和维护,且因其单线程架构,错误调试和稳定性相对较高。
  8. 多种应用场景
    • Redis 广泛应用于缓存、会话存储、实时排行榜、消息队列、社交网络的粉丝关系、地理位置索引、计数器、分布式锁等多种场景。

综上所述,Redis 凭借其高性能、丰富的数据结构、持久化选项、集群支持及广泛的客户端兼容性等特点,成为现代应用程序中不可或缺的基础设施组件之一。

二、为什么redis是单线程的还这么快?

Redis之所以作为单线程设计还能达到高速度,可以从以下几个方面理解:

  1. 内存操作
    • Redis是一个内存数据库,所有的数据都存储在内存中,这意味着它的读写速度接近于计算机内存的访问速度,远高于传统硬盘数据库的I/O速度。
  2. 避免上下文切换
    • 单线程意味着在同一时刻只处理一个客户端请求,这样就无需像多线程那样进行上下文切换。上下文切换对于多线程程序来说是一项显著6的性能消耗,尤其是在CPU核心有限的情况下,频繁的线程切换可能导致更多的等待时间和CPU缓存未命中。
  3. 多路复用技术
    • Redis使用了I/O多路复用技术(如epoll、kqueue或select/poll),可以在单个线程内监听多个socket连接。当一个连接中的请求准备好读取或写入时,Redis会立即处理,而不需要为每个连接创建独立的线程,大大提升了并发处理能力。
  4. 单线程设计简化了数据结构操作
    • 因为只有一个线程在操作数据,Redis可以避免复杂的并发控制机制(如锁),使得数据结构的操作变得简单高效。尤其是Redis中的大部分命令都可以在O(1)时间内完成,这也是其速度快的一个重要原因。
  5. 专门优化的命令执行
    • Redis针对每一条命令进行了高度优化,大部分命令都能在极短的时间内执行完毕,这种特性在单线程环境下尤为突出,因为不会有其他线程阻塞或者竞争资源。
  6. 异步操作
    • 虽然Redis的核心命令处理是单线程,但对于某些耗时操作,例如持久化、异步删除过期键以及集群间的同步等任务,Redis会使用额外的后台线程或者异步方式进行处理,不影响主线程对命令的执行。

三、redis 可以实现什么功能?

  1. 缓存

    • 最常见的用途是作为缓存系统,存储热点数据,降低数据库压力,提高整体应用性能。
  2. 会话存储

    • 用于存储Web应用或移动应用的会话信息,替代传统的session存储方式,有效应对高并发场景。
  3. 消息队列

    • Redis List 或者 Stream 结构可以用来实现简单的消息队列系统,支持先进先出(FIFO)和发布/订阅(pub/sub)模式。

    • 1. Redis List(列表)作为消息队列

      • LPUSH/RPOP:非阻塞模式,生产者使用 LPUSH 命令将消息添加到列表的头部,消费者使用 RPOP 命令从列表尾部取出消息。这种方式适用于简单的FIFO(先进先出)队列场景。
      • BRPOP:阻塞模式,消费者使用 BRPOP 命令在指定的列表上等待消息,如果列表为空,则会一直阻塞直到有消息到来。这样可以实现消费者自动感知消息的到来,减少了轮询的消耗。

      2. Redis 发布/订阅(Pub/Sub)

      • PUBLISH/SUBSCRIBE:生产者通过 PUBLISH 命令向指定的频道发布消息,消费者通过 SUBSCRIBE 命令订阅该频道,一旦有消息发布,所有订阅了该频道的消费者都会接收到消息。这是一种一对多的关系,适用于广播式的场景。
    • 存在问题:默认情况下数据无法持久化,无法保证消息准确消费(解决:手动ACK),一个消费者一个线程消耗资源过高。。。

  4. 分布式锁

    • 使用Redis的setnx命令和其他相关命令,可以实现分布式锁,确保在分布式系统中对共享资源的互斥访问。
      • 在 Redis 中实现分布式锁可以使用 SETNX EXPIRE 命令来实现,SETNX"SET if Not eXists" 的缩写,是一个原子性操作,用于在指定的 key 不存在时设置 key 的值。如果 key 已经存在,SETNX 操作将不做任何事情,返回失败;如果 key 不存在,SETNX 操作会设置 key 的值,并返回成功。而 EXPIRE 是设置锁的过期时间的,主要为了防止死锁的发生。
      • img

    其中“nx”表示 not exists 不存在则设置 key,“ex 10”表示过期时间为 10 秒,“mylock”值为 key,“lock”值为 value。

    • 存在问题:

      • 锁误删,A线程执行时间超过超时时间,锁自动删除了。此时B线程设置锁,而A可能在此时将B的锁删除。

        • 解决方案一:每个锁的 value 中添加拥有者的标识,删除之前先判断是否是自己的锁,如果是则删除,否则不删除。但是判断和删除之间不是原子性操作,所以依然有问题。此时可以使用 lua 脚本来判断并删除锁,lua 脚本可以保证 redis 中多条语句执行的原子性,所以就可以解决此问题了。
      • 死锁问题

        • SETNX 如未设置过期时间,锁忘记删了或加锁线程宕机都会导致死锁,也就是分布式锁一直被占用的情况。
      • 不可重入问题

        • 也就是说同一线程在已经获取了某个锁的情况下,如果再次请求获取该锁,则请求会失败(因为只有在第一次能加锁成功)。也就是说,一个线程不能对自己已持有的锁进行重复锁定。
      • 无法自动续期

        • 线程在持有锁期间,任务未能执行完成,锁可能会因为超时而自动释放。SETNX 无法自动根据任务的执行情况,设置新的超时实现,以延长锁的时间。

        使用 Redisson 框架来实现分布式锁。它通过简单 API 可以实现分布式锁,并且没有上述问题,它的底层也是通过 lua 脚本来实现的。

  5. 计数器

    • Redis原子性的递增和递减操作可以用于统计用户点击次数、点赞数、访问量等计数功能,原子递增与递减操作(INCR/INCRBY/DECR/DECRBY)
  6. 实时排行榜

    • 利用Redis的有序集合(Sorted Set)可以轻松实现游戏分数排行、热门商品排名等功能。

四、Redisson 框架

Redisson 是一个开源的用于操作 Redis 的 Java 框架。与 Jedis 和 Lettuce 等轻量级的 Redis 框架不同,它提供了更高级且功能丰富的 Redis 客户端。它提供了许多简化 Redis 操作的高级 API,并支持分布式对象、分布式锁、分布式集合等特性。

Redisson 特性

  1. Redisson 可以设置分布式锁的过期时间,从而避免锁一直被占用而导致的死锁问题。
  2. Redisson 在为每个锁关联一个线程 ID 和重入次数(递增计数器)作为分布锁 value 的一部分存储在 Redis 中,这样就避免了锁误删和不可重入的问题。
  3. Redisson 还提供了自动续期的功能,通过定时任务(看门狗)定期延长锁的有效期,确保在业务未完成前,锁不会被其他线程获取。

五、Redis 保证数据不丢失

Redis 保证数据不丢失的主要手段有两个:

  1. 持久化(见6:持久化)
  2. 集群运行(见十一:集群模式)

六、Redis 数据持久化

  • RDB(Redis DataBase)持久化:快照方式持久化,将某一个时刻的内存数据,以二进制的方式写入磁盘;

    优点:

    1. 速度快:相对于 AOF 持久化方式,RDB 持久化速度更快,因为它只需要在指定的时间间隔内将数据从内存中写入到磁盘上。
    2. 空间占用小:RDB 持久化会将数据保存在一个压缩的二进制文件中,因此相对于 AOF 持久化方式,它占用的磁盘空间更小。
    3. 恢复速度快:因为 RDB 文件是一个完整的数据库快照,所以在 Redis 重启后,可以非常快速地将数据恢复到内存中。
    4. 可靠性高:RDB 持久化方式可以保证数据的可靠性,因为数据会在指定时间间隔内自动写入磁盘,即使 Redis 进程崩溃或者服务器断电,也可以通过加载最近的一次快照文件恢复数据。

    缺点:

    1. 数据可能会丢失:RDB 持久化方式只能保证数据在指定时间间隔内写入磁盘,因此如果 Redis 进程崩溃或者服务器断电,从最后一次快照保存到崩溃的时间点之间的数据可能会丢失。
    2. 实时性差:因为 RDB 持久化是定期执行的,因此从最后一次快照保存到当前时间点之间的数据可能会丢失。如果需要更高的实时性,可以使用 AOF 持久化方式。
  • AOF(Append Only File)持久化:文件追加持久化,记录所有非查询操作命令,并以文本的形式追加到文件中;

优点:

  1. 数据不容易丢失:AOF 持久化方式会将 Redis 执行的每一个写命令记录到一个文件中,因此即使 Redis 进程崩溃或者服务器断电,也可以通过重放 AOF 文件中的命令来恢复数据。
  2. 实时性好:由于 AOF 持久化方式是将每一个写命令记录到文件中,因此它的实时性比 RDB 持久化方式更好。
  3. 数据可读性强:AOF 持久化文件是一个纯文本文件,可以被人类读取和理解,因此可以方便地进行数据备份和恢复操作。

缺点:

  1. 写入性能略低:由于 AOF 持久化方式需要将每一个写命令记录到文件中,因此相对于 RDB 持久化方式,它的写入性能略低。
  2. 占用磁盘空间大:由于 AOF 持久化方式需要记录每一个写命令,因此相对于 RDB 持久化方式,它占用的磁盘空间更大。
  3. AOF 文件可能会出现损坏:由于 AOF 文件是不断地追加写入的,因此如果文件损坏,可能会导致数据无法恢复。
  • 混合持久化:RDB + AOF 混合方式的持久化,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

优点

​ 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动, 同时结合 AOF 的优点,有减低了大量数据丢失的风险。

缺点

  1. 实现复杂度高:混合持久化需要同时维护 RDB 文件和 AOF 文件,因此实现复杂度相对于单独使用 RDB 或 AOF 持久化方式要高。
  2. 可读性差:AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
  3. 兼容性差:如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

七、Redis 过期策略

Redis 中的过期删除策略是指在键(key)上设置了过期时间后,Redis 在某个条件触发时会自动删除过期的键。

  • 定期删除策略(定时任务方式):Redis 会定期地(默认每秒钟检查 10 次)随机抽取一部分设置了过期时间的键,检查它们是否过期,如果过期则删除。该策略可以通过配置文件中的 hz 参数进行调整。

  • 惰性删除策略(懒汉式方式):当访问一个键时,Redis 会先检查该键是否过期,如果过期则删除。这意味着过期键可能会在访问时被删除,而不是在过期时立即删除。

Redis 从版本 4.0 开始引入了更精细化的内存管理机制:

内存淘汰机制(Redis的内存淘汰机制仅在内存不足时才会启动)
虽然 Redis 的内存管理机制很高效,但是当内存不足时,Redis 必须采取一些措施来决定哪些键值对需要被删除。这时就需要使用内存淘汰机制。Redis 提供了8种内存淘汰策略:

  1. 全局淘汰:

     1)allkeys-lru:淘汰范围是所有 keys,淘汰最久未使用的 key
     
     2)4新增-allkeys-lfu:淘汰范围是所有 keys,淘汰使用频次最少的
     
     3)allkeys-random:淘汰范围:所有 keys,随机淘汰 key
    
  2. 淘汰 expire :

     1)volatile-lru:淘汰范围:所有设置了 expire 时间的 keys,淘汰最久未使用的 key
     
     2)4新增-volatile-lfu:淘汰范围:所有设置了 expire 时间的 keys,淘汰使用频次最少的 key
     
     3)volatile-random:淘汰范围:所有设置了 expire 时间的 keys,随机淘汰 key
     
     4)volatile-ttl:淘汰范围:所有设置了 expire 时间的 keys,淘汰 ttl 剩余时间最少的 key
    
  3. 不淘汰:
    1)noeviction:不淘汰,意味着达到限制时,将无法存储

  • LRU(Least Recently Used,最近最少使用)和LFU(Least Frequently Used,最不常使用)都是常见的缓存淘汰策略,它们在选择淘汰缓存中的键时有不同的侧重点。

    1. LRU(最近最少使用):LRU 策略基于时间的概念,它认为最近被访问过的键是最有可能被再次访问的,因此在淘汰时会优先选择最久未被访问的键。LRU 策略会维护一个访问顺序列表,每当一个键被访问时,它会被移动到列表的末尾,最近没有被访问的键会位于列表的前面。当需要淘汰键时,LRU 策略会选择列表前面的键进行淘汰。

    2. LFU(最不常使用):LFU 策略基于访问频率的概念,它认为被访问次数最少的键是最不常用的,因此在淘汰时会优先选择访问次数最少的键。LFU 策略会为每个键维护一个访问计数器,每当一个键被访问时,其计数器会增加。当需要淘汰键时,LFU 策略会选择访问计数最低的键进行淘汰。

八、缓存问题

缓存雪崩:

​ 指在缓存中大量的键同时过期或失效,导致请求直接访问数据库,给数据库造成巨大压力,导致系统性能下降甚至崩溃的现象。

​ 原因:

  1. 大量缓存键同时过期:当缓存键设置了相同的过期时间,或者由于某种原因导致大量的键同时失效,会导致缓存雪崩。
  2. 缓存服务器故障:当缓存服务器发生故障,无法提供服务时,请求将直接访问后端服务,导致压力集中在后端服务上。

​ 解决:

  1. 设置随机过期时间:为缓存键设置随机的过期时间,避免大量键同时过期的情况发生,减少缓存雪崩的概率。
  2. 实现缓存预热:在系统启动或缓存失效前,提前加载热门数据到缓存中,避免在关键时刻大量请求直接访问后端服务。
  3. 使用分布式缓存:将缓存数据分布在多个缓存节点上,通过分散请求负载来减少单个缓存节点的压力,提高系统的可用性和抗压能力。
  4. 设置熔断机制:在缓存失效的情况下,通过设置熔断机制,直接返回默认值或错误信息,避免请求直接访问后端服务,减轻后端服务的压力。
  5. 采用备份机制和降级策略:在数据库压力过大时,可以暂时禁用部分非关键业务的缓存或者采用只读模式。

缓存穿透:

​ 大量的请求查询不存在于缓存和数据库中的数据,导致这些请求直接访问数据库,占用数据库资源,数据库压力增大。

  • 原因

    1. 恶意请求(异常情况):攻击者发送大量恶意请求,故意查询不存在的数据,以触发缓存穿透。
    2. 高并发请求(正常业务):当有大量的并发请求同时查询不存在的数据时,可能会导致缓存无法命中,从而触发缓存穿透。
  • 解决方式:

    • 空值缓存:即使查询结果为空,也将空值缓存一段时间,但要注意防止恶意攻击。
    • 布隆过滤器:在请求到达数据库之前,通过布隆过滤器检查是否存在对应 key,过滤掉不可能存在的 key 请求。

缓存击穿:

​ 指在缓存系统中,某个热点数据过期或失效时,同时有大量的请求访问该数据,导致请求直接访问数据库,给数据库造成巨大压力,导致系统性能下降甚至崩溃的现象。

  • 解决方式:
    • 互斥锁:在缓存失效时,通过分布式锁阻止大量请求同时查询数据库,仅让其中一个请求去数据库更新数据,完成后其他请求再从缓存获取。
    • 双重校验锁:先尝试从缓存获取数据,未获取到时加锁并再次检查缓存,仍无则查询数据库并更新缓存。
    • 设置热点数据永不过期或过期时间较长:对于一些热点数据,可以将其设置为永不过期,或者设置一个较长的过期时间,确保热点数据在缓存中可用,减少因为过期而触发的缓存击穿。

九、布隆选择器

十、Redis 高可用

  1. 持久化(见六)
  2. Redis集群(见十一)

十一、Redis 集群模式

  1. 主从复制模式 (Master-Slave Replication):
    • 在主从复制模式下,Redis实例分为主节点(Master)和从节点(Slave)。主节点接收所有写操作,从节点通过复制机制同步主节点的数据,读操作大部分都在从节点上(但对于热点数据需要从主节点读取)。主节点可以有多个从节点,从而实现数据冗余和读写分离,提升系统的可靠性和读性能。
  2. 哨兵模式 (Sentinel):
    • Redis Sentinel是一种监控和故障转移系统,用于管理Redis主从集群。哨兵节点(Sentinel instances)监控主从服务器的状态,当主节点出现故障时,Sentinel能自动选举新的主节点,并通知应用客户端新的主节点位置,实现自动化的故障转移和主备切换,保证系统的高可用性。
  3. 集群模式 - Redis Cluster模式(redis 3.0):
    • Redis Cluster是Redis官方提供的原生分布式解决方案,它采用了数据分片(sharding)的设计,能够透明地将数据分散在多个Redis节点上,每个节点被称为“槽”(slot),集群内部通过Gossip协议进行通信,实现数据分区和自动故障转移。
    • 在Cluster模式下,每个节点既是主节点也是从节点,每个主节点托管一定数量的槽,从节点复制主节点的数据,并且可以在主节点故障时自动升级为新的主节点,从而继续对外提供服务。Redis Cluster还具备数据迁移功能,能在集群扩缩容时自动调整数据分布。

注:主从模式是基石,哨兵模式在主从模式基础上解决人工故障转移的问题。

这三种模式可以根据实际的应用场景和需求灵活选择或组合使用,例如在大型系统中,可能结合主从复制+哨兵模式提供高可用,同时通过Redis Cluster实现分布式存储与水平扩展能力。

十二、本地缓存

防止缓存雪崩问题、缓存击穿问题,我们通常会采用多级缓存的解决方案,所谓的多级缓存就是:分布式缓存(Redis 或 Memcached)+本地缓存(Guava Cache 或 Caffeine)。

  1. 设置本地缓存短时间内失效

  2. 通过配置中心协调和同步

  3. 本地缓存自动更新功能

多级缓存中,本地缓存是不可或缺的组成部分,而想要保证本地缓存的数据一致性,可能采用:设置较短的本地缓存过期时间、通过配置中心来协调和同步本地缓存,以及使用本地缓存框架的自动更新功能保证数据的一致性等解决方案,而不同的业务场景,选择的解决方案也是不同的。

uster实现分布式存储与水平扩展能力。

十二、本地缓存

防止缓存雪崩问题、缓存击穿问题,我们通常会采用多级缓存的解决方案,所谓的多级缓存就是:分布式缓存(Redis 或 Memcached)+本地缓存(Guava Cache 或 Caffeine)。

  1. 设置本地缓存短时间内失效

  2. 通过配置中心协调和同步

  3. 本地缓存自动更新功能

多级缓存中,本地缓存是不可或缺的组成部分,而想要保证本地缓存的数据一致性,可能采用:设置较短的本地缓存过期时间、通过配置中心来协调和同步本地缓存,以及使用本地缓存框架的自动更新功能保证数据的一致性等解决方案,而不同的业务场景,选择的解决方案也是不同的。

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值