Redis知识总结

1.什么是Redis?

redis是一个开源的,高性能键值对存储数据库。它具有一下特点和功能:

  1. 数据结构丰富

    • 字符串(String) :这是 Redis 最基本的数据结构。它可以存储字符串、整数或者浮点数。例如,可以用字符串类型存储用户的登录令牌(token),像SET user:token:123 "abcdefg"这样的命令就可以将用户 ID 为 123 的登录令牌设置为 "abcdefg"。字符串类型还支持一些操作,如获取字符串长度(STRLEN)、追加内容(APPEND)等。
    • 列表(List) :Redis 的列表是一个有序的字符串集合,可以在列表的两端进行插入(LPUSH 和 RPUSH)和弹出(LPOP 和 RPOP)操作。例如,在一个消息队列应用中,可以使用列表来存储消息。LPUSH messages "message1"可以将 "message1" 插入到名为 "messages" 的列表头部。
    • 集合(Set) :集合是一个无序的、不包含重复元素的字符串集合。可以进行添加元素(SADD)、删除元素(SREM)、检查元素是否存在(SISMEMBER)等操作。比如,要存储一个用户关注的其他用户列表,可以使用集合。SADD user:1:followings user:2 user:3就表示用户 1 关注了用户 2 和用户 3。
    • 有序集合(Sorted Set) :它和集合类似,但是每个元素都关联了一个分数(score),可以根据分数对元素进行排序。常用于排行榜等场景。例如,在一个游戏的排行榜中,ZADD leaderboard 100 user:1 200 user:2表示用户 1 的分数是 100,用户 2 的分数是 200,并且可以通过ZRANGE leaderboard 0 -1 WITHSCORES来获取排行榜上所有用户及其分数。
    • 哈希(Hash) :哈希是一个键值对集合,适合存储对象。例如,存储一个用户对象的信息,HMSET user:1 name "John" age 30可以将用户 1 的姓名设置为 "John",年龄设置为 30,并且可以通过HGET user:1 name来获取用户 1 的姓名。
  2. 高性能

    Redis 是基于内存存储数据的,内存的读写速度远远快于磁盘。这使得 Redis 能够快速地处理大量的数据操作请求,如读写数据、执行数据结构相关的操作等。它通常可以在单台服务器上处理每秒数十万甚至更多的请求,能够很好地满足对性能要求极高的应用场景,如缓存系统、实时排行榜等。

  3. 持久化机制

    • RDB(Redis Database)持久化:RDB 是一种快照式的持久化方式。Redis 会在指定的时间间隔内,将内存中的数据以二进制的形式保存到磁盘上。例如,可以通过配置文件设置每隔一定时间(如 15 分钟)或者当数据修改达到一定次数(如 100 次)就生成一个 RDB 文件。这种方式在恢复数据时速度比较快,但是可能会丢失最后一次快照之后的数据。
    • AOF(Append Only File)持久化 :AOF 持久化是通过记录 Redis 执行的所有写命令来实现持久化的。每当有数据修改的命令执行时,该命令就会被追加到 AOF 文件的末尾。例如,每次执行SETLPUSH等写命令时,都会记录在 AOF 文件中。在恢复数据时,只要重新执行 AOF 文件中的所有写命令就可以恢复数据。AOF 方式可以更好地保证数据的完整性,但文件可能会因为记录大量的命令而变得很大。
  4. 支持事务

    Redis 的事务允许在一个单独的步骤中执行一组命令,并且可以保证这些命令要么全部执行成功,要么全部不执行。通过MULTI命令开始一个事务,然后可以添加多个命令,最后通过EXEC命令来执行事务中的所有命令。不过需要注意的是,Redis 的事务和传统关系数据库中的事务有一些区别,例如 Redis 事务没有回滚机制(在某些特定错误情况下)。

  5. 支持发布 / 订阅(Pub/Sub)模式

    在这种模式下,客户端可以订阅一个或多个频道(channel),当有其他客户端向这些频道发布消息时,订阅的客户端就可以收到消息。例如,一个新闻发布系统可以有多个客户端订阅 "sports"、"technology" 等频道,当有新的体育新闻或者科技新闻发布(向相应频道发布消息)时,订阅客户端就可以获取最新消息,实现消息的实时推送。

  6. 应用场景广泛

    • 缓存应用:由于 Redis 的高性能,它被广泛用于缓存数据库查询结果、网页内容等。比如,在一个电商网站中,商品详情页的数据可以缓存到 Redis 中,当用户再次请求该页面时,可以直接从 Redis 中获取数据,减少数据库的访问压力,提高响应速度。
    • 计数器应用:可以用于统计网站的访问量、用户的点赞数等。例如,每次有用户访问一个网页,就可以使用 Redis 的原子操作(如 INCR)对访问量计数器进行加 1 操作。
    • 分布式锁应用:在分布式系统中,Redis 可以用于实现分布式锁。例如,多个服务可能需要访问同一个共享资源,通过在 Redis 中设置一个锁(可以使用 SETNX 命令),只有获取到锁的服务才能访问资源,从而避免数据冲突。

2.redis为什么这么快?

  1. 基于内存存储

    • 内存的读写速度比磁盘快几个数量级。Redis 将数据存储在内存中,数据的读取和写入操作可以直接在内存中快速完成。例如,从内存中读取一条数据可能只需要几纳秒到几十纳秒,而从磁盘读取相同的数据可能需要几毫秒甚至更长时间。这使得 Redis 在处理数据操作时能够快速响应,尤其是在处理频繁的读写操作时,如缓存场景下的热门数据读取,能够在极短的时间内返回结果。
  2. 高效的数据结构实现

    • 简单且优化的数据结构:Redis 的核心数据结构(如字符串、列表、集合、有序集合和哈希)在设计上非常简洁高效。以字符串为例,Redis 字符串的实现是一个简单的字节数组,这种简单的结构使得对字符串的操作(如读取、写入、追加等)能够以很低的开销完成。对于列表,Redis 采用了双向链表或者压缩列表(根据列表长度和元素大小动态选择)的实现方式,在两端进行插入和弹出操作时具有很高的效率。
    • 数据结构操作的时间复杂度优化:Redis 的数据结构操作在设计上尽可能保证了较低的时间复杂度。例如,在集合操作中,检查一个元素是否属于集合(SISMEMBER)的操作时间复杂度是 O (1),这是因为 Redis 使用哈希表来实现集合,通过对元素进行哈希计算可以快速定位元素是否存在。在有序集合中,添加元素(ZADD)和查询元素(ZRANGE)等操作也具有相对较低的时间复杂度,这是通过使用跳跃表等数据结构实现的。
  3. 单线程架构(在核心处理部分)

    • 避免线程切换开销:Redis 在核心的数据处理部分是单线程的。在多线程环境下,线程之间的切换会带来一定的开销,包括保存和恢复线程上下文、调度等操作。而 Redis 单线程避免了这种开销,使得 CPU 能够专注于处理数据操作。例如,当处理大量的读写请求时,不需要在多个线程之间进行频繁的切换,从而提高了处理效率。
    • 避免数据竞争和锁开销:多线程操作共享数据时,通常需要使用锁来保证数据的一致性。锁的获取和释放会带来额外的开销,并且可能导致线程阻塞。Redis 的单线程架构避免了数据竞争的问题,不需要使用复杂的锁机制来协调多个线程对数据的访问,从而减少了这部分开销。不过,需要注意的是,Redis 的单线程主要是指在处理命令的核心部分,实际上它在一些后台任务(如持久化等)还是会用到多线程。
  4. I/O 多路复用技术

    • 高效的网络 I/O 处理:Redis 使用 I/O 多路复用技术(如 epoll 在 Linux 系统上)来处理多个客户端连接。I/O 多路复用允许一个进程同时监听多个文件描述符(在网络通信中,代表客户端连接),当有数据可读或者可写时,能够快速地通知进程进行处理。这样,Redis 可以同时处理大量的客户端请求,而不需要为每个客户端请求创建一个新的线程或者进程。例如,一个 Redis 服务器可以同时处理数千个客户端的连接请求,并且能够高效地在这些请求之间进行切换,快速地响应每个客户端的读写请求。

3.Redis的应用场景

  1. 缓存应用场景

    • 网页缓存:对于频繁访问的网页内容,如电商网站的商品详情页、新闻网站的热门新闻页面等,可以将页面内容缓存到 Redis 中。当用户请求这些页面时,首先从 Redis 中查找。如果命中缓存,就直接返回缓存内容,大大减少了数据库查询和页面渲染的时间,提高了网站的响应速度。例如,一个电商网站的商品详情页通常包含商品信息、图片、用户评价等数据,将这些数据缓存到 Redis 后,下次用户访问该商品详情页时,能快速获取数据,有效减轻数据库的负载。
    • 数据库查询结果缓存:在应用程序中,对于一些复杂且频繁查询的数据库操作(如关联多个表进行统计分析),可以将查询结果缓存到 Redis。比如,在一个企业资源规划(ERP)系统中,对于销售数据的统计报表查询,如果每次都从数据库中重新计算,会消耗大量的时间和资源。将查询结果缓存到 Redis 后,下次相同的查询请求就可以直接从 Redis 获取结果,只有当数据发生变化时才重新查询数据库并更新缓存。
    • 分布式缓存:在分布式系统中,多个应用服务器可能共享数据。Redis 作为分布式缓存,可以存储共享的数据,确保各个服务器获取的数据一致性。例如,在一个微服务架构的电商系统中,多个微服务(如用户服务、商品服务、订单服务)可能都需要访问用户的购物车信息,将购物车信息存储在 Redis 中,各个微服务就可以方便地共享和更新这些信息。
  2. 计数器应用场景

    • 网站访问量统计 :可以使用 Redis 的原子操作(如 INCR 命令)来统计网站的访问量。每当有用户访问网站的页面,就在 Redis 中对应的计数器上加 1。例如,对于一个博客网站,通过在 Redis 中设置一个名为 "blog:page:views" 的计数器,每次用户访问博客文章页面时,就执行INCR blog:page:views操作,方便快捷地统计每篇文章的访问量。
    • 用户行为统计:统计用户的点赞数、评论数、分享数等行为。在社交平台上,为每个用户的动态(如微博、朋友圈动态)设置点赞计数器,通过 Redis 来记录和更新点赞数。比如,当用户点赞一条微博时,使用 Redis 的原子操作增加该微博对应的点赞计数器的值,并且可以实时地展示给用户最新的点赞数量。
  3. 消息队列应用场景

    • 简单消息队列:Redis 的列表(List)数据结构可以用于实现简单的消息队列。生产者将消息通过 LPUSH 或者 RPUSH 命令添加到列表的一端,消费者通过 LPOP 或者 RPOP 命令从列表的另一端获取消息。例如,在一个日志收集系统中,日志收集客户端可以将收集到的日志作为消息通过 LPUSH 添加到名为 "log:queue" 的列表中,而日志处理服务可以通过 RPOP 从队列中获取日志消息进行处理。
    • 优先级消息队列:结合有序集合(Sorted Set)可以实现优先级消息队列。给消息设置不同的优先级分数,消费者按照分数顺序获取消息。比如,在一个任务调度系统中,紧急任务可以设置较高的优先级分数,放入有序集合中,系统根据优先级从高到低依次处理任务。
  4. 排行榜应用场景

    • 游戏排行榜 :在游戏中,可以使用 Redis 的有序集合(Sorted Set)来实现排行榜。将玩家的分数作为有序集合的分数,玩家 ID 作为成员。例如,在一个赛车游戏中,玩家完成比赛后,根据比赛成绩(分数)使用ZADD leaderboard score player_id命令将玩家的成绩添加到名为 "leaderboard" 的排行榜中。可以通过ZRANGE leaderboard 0 -1 WITHSCORES命令获取排行榜的前几名玩家及其分数,方便地展示游戏中的排行榜。
    • 电商商品热度排行榜:在电商平台上,根据商品的浏览量、销量等因素综合计算商品的热度分数,使用有序集合来构建商品热度排行榜。这样可以展示热门商品,吸引用户购买,并且随着用户的浏览和购买行为,排行榜能够实时更新。
  5. 分布式锁应用场景

    • 资源访问控制 :在分布式系统中,多个节点可能会同时访问同一个共享资源,如数据库中的同一行记录、分布式文件系统中的同一个文件等。通过 Redis 的 SETNX(SET if Not eXists)命令可以实现分布式锁。当一个节点想要访问共享资源时,先尝试使用 SETNX 在 Redis 中设置一个锁(如SETNX resource_lock 1),如果返回 1,表示成功获取锁,可以访问资源;如果返回 0,表示锁已经被其他节点获取,需要等待。这样可以有效地避免多个节点同时访问资源导致的数据冲突。
    • 服务调度协调:在微服务架构中,不同的微服务之间可能需要协调工作。例如,在一个数据处理流程中,有数据采集微服务、数据清洗微服务和数据存储微服务。可以使用分布式锁来确保在某个时刻只有一个微服务在对数据进行操作,避免数据混乱。

4.Redis持久化

可以看我之前的博客

Redis持久化

5.Redis过期键删除策略

  1. 定时删除
    • 原理
      • 定时删除策略是在设置键的过期时间的同时,创建一个定时器(timer)。当定时器到期时,由定时器的回调函数自动执行对键的删除操作。这样可以保证过期键能够被及时删除,从而有效地节省内存空间。
    • 优点
      • 内存友好。能够及时释放过期键所占用的内存,使得内存的使用效率较高。例如,对于一个存储大量临时数据的 Redis 应用场景,如限时促销活动的缓存数据,当活动结束后,相关键的过期时间一到就立刻删除,能避免内存浪费。
    • 缺点
      • CPU 时间消耗大。如果在 Redis 中有大量的键设置了过期时间,那么创建和维护大量的定时器会占用相当多的 CPU 资源。这可能会影响 Redis 服务器的整体性能,尤其是在高并发的场景下,频繁地处理定时器回调可能会导致服务器响应变慢。
  2. 惰性删除
    • 原理
      • 惰性删除是指当客户端访问一个键时,Redis 会检查这个键是否过期。如果过期,就立即删除该键;如果没有过期,则正常返回该键对应的值。这种策略不会主动去检查和删除过期键,只有在需要访问键的时候才进行过期检查。
    • 优点
      • CPU 友好。由于不会主动进行过期键的检查和删除,只有在实际访问键时才进行处理,所以对 CPU 资源的消耗比较小。这在 CPU 资源紧张的环境下比较有利,能够避免因频繁的过期键检查而占用过多的 CPU 时间。
    • 缺点
      • 内存不友好。如果一个过期键在很长一段时间内都没有被访问,那么它就会一直占用内存空间。这可能导致内存空间的浪费,尤其是在有大量过期键但很少被访问的情况下,内存可能会被这些过期键耗尽。
  3. 定期删除
    • 原理
      • 定期删除是一种折中的策略。Redis 会每隔一段时间(这个时间间隔可以通过配置参数来设定),对数据库进行一次检查,随机抽取一定数量的键,检查这些键是否过期。如果过期,就删除这些键。通过定期地执行这种检查和删除操作,既能避免内存的过度浪费,又能在一定程度上控制 CPU 的消耗。
    • 优点
      • 平衡了内存和 CPU。它不像定时删除那样消耗过多的 CPU 资源,也不像惰性删除那样可能导致内存长期被过期键占用。通过定期地清理过期键,能够在一定程度上保证内存的合理使用,同时不会对 CPU 造成过大的负担。
    • 缺点
      • 难以确定最佳的检查周期和检查数量。如果检查周期过长或者每次检查的键数量过少,可能会导致内存中过期键不能及时被清理;反之,如果检查周期过短或者每次检查的键数量过多,又会增加 CPU 的负担。而且,这种策略可能会遗漏一些过期键,因为它只是随机抽取部分键进行检查。

在实际的 Redis 应用中,Redis 采用的是惰性删除和定期删除相结合的策略。这样可以在保证内存不会被大量过期键占用的同时,也能尽量减少对 CPU 资源的浪费。

6. Redis的内存淘汰策略

  1. noeviction(不淘汰)
    • 策略描述
      • 当内存使用达到设置的最大内存限制时,Redis 不进行数据淘汰。对于写操作,Redis 直接返回错误信息。这是一种比较保守的策略,主要用于对数据完整性要求极高,不能丢失任何数据,并且不希望进行数据淘汰的场景。
    • 适用场景
      • 例如在一些对数据准确性要求极高的金融系统的缓存应用中,如存储交易记录等关键数据的缓存。如果缓存已满,宁愿停止写入操作并返回错误,也不能丢失或淘汰已有的数据记录。
  2. volatile - lru(易失性 - 最近最少使用)
    • 策略描述
      • 这种策略主要用于设置了过期时间的键(易失性键)。它会从设置了过期时间的键集合中,挑选出最近最少使用的键进行淘汰。LRU(Least Recently Used)的实现是通过一个近似的 LRU 算法,Redis 会为每个键记录它的最后一次访问时间,当需要淘汰键时,根据这些记录来选择最近最少使用的键。
    • 适用场景
      • 适合用于缓存场景,比如一个电商网站的商品详情缓存。商品详情缓存通常会设置过期时间,当内存不足时,优先淘汰那些最近很少被用户访问的商品详情缓存键,从而为新的商品详情缓存腾出空间。
  3. volatile - ttl(易失性 - 剩余存活时间)
    • 策略描述
      • 同样是针对设置了过期时间的键。它会优先淘汰那些剩余存活时间(Time - To - Live,TTL)最短的键。即从所有设置了过期时间的键中,选择距离过期时间最近的键进行淘汰。
    • 适用场景
      • 假设一个限时抢购活动的缓存场景,其中各种促销商品的缓存信息都设置了不同的过期时间。当内存紧张时,这种策略会优先淘汰那些快要过期的促销商品缓存,以保证更长期限的促销商品缓存能够继续保留。
  4. volatile - random(易失性 - 随机)
    • 策略描述
      • 也是针对设置了过期时间的键。当需要淘汰键时,它会在设置了过期时间的键集合中随机选择一个键进行淘汰。这种策略相对比较简单,没有考虑键的使用频率或者剩余存活时间等因素。
    • 适用场景
      • 适用于对数据淘汰的顺序没有特别要求,且数据具有相似的重要性和使用频率的场景。例如在一个小型的、临时性的缓存应用中,如为一次线上抽奖活动设置的奖品信息缓存,每个奖品信息缓存的重要性和使用频率大致相同,当需要淘汰时随机选择即可。
  5. allkeys - lru(所有键 - 最近最少使用)
    • 策略描述
      • 这种策略会从所有的键(不管是否设置了过期时间)中挑选出最近最少使用的键进行淘汰。和 volatile - lru 类似,也是使用近似的 LRU 算法来判断键的使用情况。
    • 适用场景
      • 在通用的缓存场景中应用广泛。例如在一个大型的内容分发网络(CDN)缓存系统中,缓存了大量的网页资源、图片等。当内存不足时,根据 LRU 原则淘汰那些最近最少被请求的资源缓存,从而为新的缓存资源腾出空间。
  6. allkeys - random(所有键 - 随机)
    • 策略描述
      • 从所有的键(不管是否设置了过期时间)中随机选择一个键进行淘汰。这是一种简单粗暴的淘汰策略,不考虑键的任何特性,如使用频率、是否有过期时间等。
    • 适用场景
      • 适用于对数据淘汰没有精细要求,且所有数据的重要性相对均衡的场景。比如在一个简单的键值存储系统的缓存应用中,存储了各种类型的数据,如用户信息、配置信息等,当需要淘汰键时随机选择,因为这些键的重要性和使用频率差异不大。

7.redis事务

  1. 事务的概念
    • 定义:Redis 事务是一个命令集合,这些命令会被当作一个整体按顺序地执行,就像一个原子操作一样。在事务执行期间,Redis 不会中断事务去执行其他客户端的命令,直到事务中的所有命令都执行完毕或者遇到错误而回滚(部分情况)。
    • 目的:主要用于保证一组操作的原子性,确保这些操作要么全部成功,要么全部失败。例如,在一个电商系统的库存管理和订单生成场景中,如果要同时更新库存数量和生成订单记录,使用事务可以避免在更新库存后,因某些原因(如网络故障、服务器错误等)无法生成订单,从而导致数据不一致的情况。
  2. 事务的基本操作
    • MULTI 命令
      • 这是事务的开始标志。当客户端发送 MULTI 命令后,Redis 会将后续的命令放入一个事务队列中,而不是立即执行这些命令。例如,客户端发送 "MULTI" 命令后,再发送 "SET key1 value1" 和 "SET key2 value2",此时 Redis 不会执行这两个 SET 命令,而是将它们添加到事务队列中。
    • EXEC 命令
      • 用于执行事务队列中的所有命令。当客户端发送 EXEC 命令时,Redis 会按照事务队列中命令的顺序依次执行这些命令。如果所有命令都成功执行,Redis 会返回一个包含每个命令执行结果的数组;如果在执行过程中某个命令出现错误,根据错误的类型,可能会有不同的处理方式(下面会详细介绍错误处理)。
    • DISCARD 命令
      • 用于取消一个事务。如果客户端在发送 MULTI 命令后,还没有发送 EXEC 命令之前,想要放弃这个事务,就可以发送 DISCARD 命令。发送 DISCARD 命令后,Redis 会清空事务队列中的所有命令,事务就此取消。
  3. 事务的错误处理
    • 语法错误
      • 如果在事务队列中的命令出现语法错误,例如命令拼写错误或者参数数量错误等,当执行 EXEC 命令时,Redis 会直接返回错误信息,并且不会执行事务队列中的任何命令。例如,在事务队列中有一个命令 "SETT key value"(正确的应该是 "SET key value"),当发送 EXEC 命令时,Redis 会发现这个语法错误,然后返回错误,整个事务中的所有命令都不会被执行。
    • 运行时错误
      • 如果命令在执行过程中出现运行时错误,比如对一个不存在的数据类型进行操作,Redis 的处理方式和语法错误不同。Redis 会继续执行事务队列中后续的命令,并且在返回结果时,会将出现错误的命令的结果标记为错误,其他命令的结果正常返回。例如,在事务队列中有 "SET key1 value1" 和 "INCR key1"(假设 key1 存储的是一个字符串,不能进行自增操作),当执行 EXEC 命令时,Redis 会先执行 "SET key1 value1" 成功,然后执行 "INCR key1" 出现运行时错误,最后返回的结果数组中,第一个元素是 "SET key1 value1" 的成功结果,第二个元素是 "INCR key1" 的错误结果。
  4. 事务的实现原理(基于乐观锁)
    • WATCH 命令
      • Redis 事务采用了一种乐观锁的机制来实现数据的一致性。WATCH 命令用于在事务开始之前,监视一个或多个键。如果在事务执行之前,被监视的键的值被其他客户端修改了,那么当这个事务执行 EXEC 命令时,Redis 会取消这个事务的执行。例如,有两个客户端 Client1 和 Client2,Client1 先使用 "WATCH key" 命令监视 key,然后开始一个事务(发送 MULTI 命令),在事务队列中放入 "SET key value1" 命令。此时,如果 Client2 修改了 key 的值(发送 "SET key value2"),那么当 Client1 发送 EXEC 命令时,Redis 会发现 key 的值已经被修改,于是取消 Client1 的事务执行。
    • 事务执行过程中的数据一致性保证
      • 通过 WATCH 命令和事务的结合,Redis 在一定程度上保证了数据的一致性。当一个事务被取消(因为被监视的键被修改)后,客户端可以重新监视键并重新开始事务。这种机制类似于数据库中的乐观锁,在没有冲突的情况下可以顺利执行事务,在出现可能导致数据不一致的情况时,通过取消事务来避免错误的操作。

8. Redis哈希槽概念

  1. 哈希槽的概念

    • 定义:Redis 集群使用哈希槽(Hash Slot)来分配数据。整个哈希槽的范围是 0 - 16383,总共 16384 个哈希槽。当向 Redis 集群中写入数据时,会根据键(key)计算出一个哈希值,然后通过这个哈希值对 16384 取模,得到这个键对应的哈希槽编号。例如,对于一个键为 "user:1" 的键,先计算它的哈希值,假设为 32768,然后 32768 % 16384 = 0,那么这个键就会被分配到 0 号哈希槽。
    • 目的:这种设计主要是为了方便数据在集群中的分布式存储和管理。通过哈希槽可以将数据均匀地分布在多个节点上,避免数据集中存储在少数节点而导致负载不均衡的情况。而且在集群进行扩容或者缩容时,方便进行数据的重新分配。
  2. 哈希槽与节点的关系

    • 节点分配哈希槽:在 Redis 集群中,每个节点负责一部分哈希槽。例如,一个简单的 3 - node(3 个节点)Redis 集群,可以将 16384 个哈希槽分配给这 3 个节点,如节点 1 负责 0 - 5461 个哈希槽,节点 2 负责 5462 - 10923 个哈希槽,节点 3 负责 10924 - 16383 个哈希槽。这些分配信息会在集群的配置文件或者节点之间的通信中记录下来。
    • 数据存储与查询:当客户端向集群发送一个键值存储请求时,集群会先根据键计算出哈希槽编号,然后判断这个哈希槽由哪个节点负责,再将请求转发到相应的节点进行存储。在查询数据时,也是同样的过程,先确定哈希槽,再找到对应的节点获取数据。比如,客户端请求存储键 "product:100",经过计算哈希槽编号后发现该哈希槽属于节点 2,那么这个键值对就会存储在节点 2 上;当客户端查询 "product:100" 时,集群也会找到节点 2 来获取数据。
  3. 哈希槽的重新分配(集群伸缩时)

    • 扩容情况:当向 Redis 集群添加新节点时,需要将部分哈希槽从原来的节点重新分配到新节点上。例如,原来有 3 个节点的集群,现在添加了一个新节点。可以将其他节点上的部分哈希槽迁移到新节点,以实现数据的重新平衡。这通常可以使用 Redis - trib.rb 等工具来进行操作。在重新分配哈希槽的过程中,集群仍然可以正常工作,不过在哈希槽迁移期间,可能会对性能有一定的影响,因为涉及到数据的移动和重新索引。
    • 缩容情况:当从集群中移除一个节点时,这个节点所负责的哈希槽需要被重新分配到其他剩余的节点上。在缩容操作前,需要先将被移除节点上的数据备份或者迁移到其他节点,以避免数据丢失。同样,在缩容过程中,也会对集群的性能产生一定的影响,并且需要谨慎操作,确保数据的完整性。

9.Redis数据分区

  1. 分区的概念与目的

    • 概念:Redis 分区是一种将数据分散存储在多个 Redis 实例(或节点)中的技术。它通过一定的分区规则,把数据划分到不同的分区,每个分区可以独立地存储和管理数据。
    • 目的
      • 提升存储容量:单个 Redis 实例的存储容量是有限的,通过分区可以突破这个限制。例如,当数据量不断增长,超出单个 Redis 实例所能承载的范围时,采用分区可以将数据分布到多个实例中,从而能够存储更多的数据。
      • 提高性能:分区可以将数据负载分散到多个节点,减少单个节点的读写压力。在高并发场景下,不同的客户端请求可以被分配到不同的分区进行处理,这样能够有效提升系统的整体读写速度和吞吐量。
  2. 分区的方式

    • 范围分区(Range Partitioning)
      • 原理:根据键(key)的范围将数据划分到不同的分区。例如,对于存储用户订单数据的 Redis,可以按照订单编号的范围进行分区。假设订单编号是从 1 开始的连续整数,那么可以将订单编号 1 - 10000 的订单数据存储在一个分区,10001 - 20000 的订单数据存储在另一个分区,以此类推。
      • 优点:易于理解和实现。对于一些具有明显范围特征的数据,这种分区方式能够很好地将数据划分开来,方便管理和维护。
      • 缺点:可能会出现数据倾斜的情况。如果数据的分布不均匀,例如某些范围内的数据量远远大于其他范围,就会导致某些分区负载过重,而其他分区资源闲置。
    • 哈希分区(Hash Partitioning)
      • 原理:通过对键进行哈希运算来确定数据所属的分区。Redis 集群中的哈希槽(Hash Slot)就是一种哈希分区的应用。例如,对于键 "user:1",先对其进行哈希运算,得到一个哈希值,然后根据这个哈希值与预先设定的分区规则(如取模运算)来确定它属于哪个分区。如果有 4 个分区,哈希值对 4 取模,得到的余数为 0 则属于分区 0,余数为 1 则属于分区 1,以此类推。
      • 优点:能够比较均匀地分配数据,避免数据倾斜。只要哈希函数设计合理,数据就可以均匀地分布在各个分区中,使得每个分区的负载相对均衡。
      • 缺点:增加了复杂性。需要设计合适的哈希函数来确保数据的均匀分配,而且在进行数据迁移(如添加或删除分区)时,可能需要重新计算哈希值和重新分配数据,这会增加系统的复杂性和维护成本。
  3. 分区的挑战与应对策略

    • 数据一致性挑战
      • 当数据分布在多个分区时,保持数据一致性变得更加复杂。例如,在一个分布式事务涉及多个分区的情况下,需要确保所有分区的数据更新都成功或者都失败。可以采用分布式事务协议(如两阶段提交协议)来解决这个问题,不过这会增加系统的复杂性和性能开销。
    • 数据迁移挑战
      • 在添加或删除分区时,需要进行数据迁移。在迁移过程中,可能会影响系统的正常运行。为了减少对系统的影响,可以采用渐进式数据迁移的方式,在后台逐步将数据从一个分区迁移到另一个分区,同时确保数据的完整性和系统的可用性。
    • 客户端复杂性增加
      • 分区后,客户端需要知道如何与多个分区进行交互。可以通过使用客户端分区策略来简化客户端的操作,例如,客户端可以使用连接池来管理与多个分区的连接,并且通过一定的负载均衡算法来分配请求到不同的分区。

关于redis缓存,哨兵,集群方面的问题,可以看下下面的链接博客~

10.Redis缓存

Redis缓存问题

11.Redis哨兵机制

Redis哨兵机制

12.Redis集群

Redis集群

13.如何保证缓存和数据库双写时的数据一致性

  1. 先更新数据库,再更新缓存

    • 问题分析
      • 这种方式可能会导致数据不一致。如果在更新数据库后,更新缓存之前,有其他请求读取缓存,就会读到旧的数据。例如,数据库中原数据为 1,先将数据库更新为 2,此时缓存还没更新,其他请求读取缓存得到的数据依然是 1,产生数据不一致。
    • 解决方案及适用场景
      • 可以使用分布式锁来限制同一时间只有一个请求能够进行双写操作。比如在更新数据库和缓存的过程中,通过获取如 Redis 分布式锁来保证顺序性。适用于对一致性要求极高,且并发量相对较小的场景,因为分布式锁会对性能产生一定影响。
  2. 先更新缓存,再更新数据库

    • 问题分析
      • 同样容易出现数据不一致的情况。假设缓存更新为新值后,数据库更新失败,此时缓存中的数据是新值,而数据库是旧值。并且如果有其他请求在数据库更新失败后读取数据,会将错误的数据写入缓存,进一步扩大数据不一致的情况。
    • 解决方案及适用场景
      • 可以采用消息队列(MQ)。在更新缓存后,将数据库更新操作作为一个消息发送到 MQ 中,由消费者来确保数据库更新成功。如果更新失败,可以通过重试机制来保证数据库最终能够更新成功。这种方式适用于允许短暂数据不一致,且系统中有消息队列基础设施的场景。
  3. 先删除缓存,再更新数据库

    • 问题分析
      • 可能会出现缓存穿透和数据不一致的问题。如果在删除缓存后,更新数据库之前,有其他请求读取数据,由于缓存已被删除,就会穿透到数据库读取旧数据,并且会将旧数据重新写入缓存,导致数据不一致。
    • 解决方案及适用场景
      • 可以采用延迟双删策略。在更新数据库后,等待一小段时间(如几百毫秒)后再次删除缓存。这样可以尽量减少在更新数据库期间,旧数据被重新写入缓存的情况。适用于读多写少的场景,因为写操作会比较复杂,需要进行两次缓存删除操作。
  4. 先更新数据库,再删除缓存

    • 问题分析
      • 大部分情况下能保证数据一致性,但也可能出现问题。如果数据库更新成功后,删除缓存操作失败,缓存中的数据就会成为旧数据,导致数据不一致。
    • 解决方案及适用场景
      • 可以将删除缓存操作放入消息队列中。当数据库更新成功后,发送一个删除缓存的消息到 MQ 中,由消费者来执行删除缓存操作。这样即使数据库更新后的删除缓存操作失败,也可以通过消息队列的重试机制来确保缓存最终被删除。这种方式适用于对一致性要求较高,同时读写比较均衡的场景。
  5. 使用数据库的事务机制和缓存的过期时间配合

    • 问题分析
      • 单纯依靠数据库事务不能保证缓存和数据库的一致性,因为事务只能保证数据库操作的原子性。而缓存过期时间如果设置不合理,可能会导致缓存数据长时间不一致。
    • 解决方案及适用场景
      • 当更新数据库时,开启数据库事务,保证数据库操作的正确执行。同时,合理设置缓存的过期时间,当缓存过期后,下一次读取会从数据库中获取最新数据,从而更新缓存。这种方式适用于对数据一致性要求不是特别高,允许缓存数据在一定时间内稍旧的场景,如一些非关键数据的缓存。

14.假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 命令(不推荐用于生产环境大规模查找)

  • 原理及操作方式
    Redis 提供了keys命令,它支持使用通配符来匹配符合特定模式的 key。例如,如果要查找以prefix_开头的 key,可以使用keys prefix_*命令。在客户端中执行该命令后,Redis 会遍历所有的 key,然后返回匹配该前缀模式的所有 key。对于有 1 亿个 key 的情况,虽然能准确找出以指定前缀开头的 key,但这个命令是阻塞式的,会在执行期间对 Redis 服务器的性能产生较大影响,尤其是在大规模数据场景下,可能会导致 Redis 服务在较长时间内无法响应其他请求,甚至出现卡顿等情况,所以一般不建议在生产环境的大规模数据查找中使用。

使用 scan 命令(推荐用于生产环境)

  • 原理及操作方式
    scan命令是一种渐进式遍历的方式,它不会像keys命令那样一次性阻塞式地遍历所有 key,而是以游标(cursor)的方式分多次、小批量地返回匹配的数据。其基本使用语法如下:

    scan 0 match prefix_* count 1000

这里的0是初始游标值,表示从开始遍历;match后面跟着的是匹配的模式,即要查找的以特定前缀开头的模式;count参数指定了每次返回的大致数量(实际返回数量可能会有波动)。例如,第一次执行上述命令后,会返回一个包含两个元素的数组,第一个元素是下一次扫描的游标值,第二个元素是一个包含本次扫描匹配到的部分 key 的列表。然后可以拿着新的游标值继续执行scan命令,不断重复这个过程,直到游标值为0,表示遍历完成,最终可以收集到所有以指定前缀开头的 key。这种方式对 Redis 服务器性能的影响相对较小,适用于生产环境下大规模数据中查找符合特定前缀的 key。

15.分布式锁

分布式锁

以上就是对Redis的知识总结,有不足的地方希望大家指正,谢谢大家观看~~

相关推荐
cooldream200918 分钟前
数据库模型全解析:从文档存储到搜索引擎
数据库·人工智能·搜索引擎·知识图谱
傻啦嘿哟1 小时前
用Python实现简单的任务自动化
网络·数据库·python
想要入门的程序猿7 小时前
Qt菜单栏、工具栏、状态栏(右键)
开发语言·数据库·qt
键盘上的蚂蚁-7 小时前
Python 语言结合 Flask 框架来实现一个基础的代购商品管理
jvm·数据库·oracle
代码欢乐豆8 小时前
MongoDB的部署和操作
数据库·mongodb
<e^πi+1=0>8 小时前
使用Locust对MongoDB进行负载测试
数据库·mongodb
文浩(楠搏万)8 小时前
Java内存管理:不可达对象分析与内存泄漏优化技巧 Eclipse Memory Analyzer
java·开发语言·缓存·eclipse·内存泄漏·不可达对象·对象分析
圆蛤镇程序猿8 小时前
【什么是MVCC?】
java·数据库·oracle
开心邮递员8 小时前
sql server: split 函数;cross apply操作符
数据库·sql
老大白菜8 小时前
PostgreSQL 内置函数
数据库·postgresql