文章目录
Redis知识点学习(一)
Redis常用的数据结构
Redis支持多种类型的数据结构,主要包括:
- 字符串(Strings):最基本的数据类型,可以存储文本或二进制数据,常用于缓存、计数等。
- 哈希(Hashes):键值对集合,适用于存储对象。
- 列表(Lists):有序集合,支持在两端推入或弹出元素,适合实现队列、栈等功能。
- 集合(Sets):不重复元素的集合,适用于去重、交集、并集等操作。
- 有序集合(Sorted Sets):集合中的每个元素都会关联一个分数,适用于排行榜等需要排序的场景。
- 位图(Bitmaps):通过位操作处理二进制数据,适合进行统计计数等。
- 超日志(HyperLogLogs):用于估算集合中唯一元素的数量,适用于大数据量的去重统计。
- 地理空间(Geospatial):存储地理位置信息,并进行范围查询、距离计算等。
Redis集群和分片
Redis集群(Cluster)和分片(Sharding)是两种用于提升Redis性能和可用性的技术。它们通过在多个节点上分布数据来实现高性能和数据的高可用性。
Redis集群
Redis集群通过将数据自动分割到多个节点上来提供一种方式,以实现数据的自动分片和高可用性。
它允许客户端执行命令,就像操作一个统一的Redis实例一样,而实际上数据是分布在多个节点上的。
- 自动分片:Redis集群自动将数据分布在不同的节点上,每个节点只保存部分数据。
- 高可用性:集群通过使用多个副本来确保高可用性。即使某些节点发生故障,集群仍然可以继续工作。
- 无缝扩展和收缩:可以在不停止服务的情况下,向集群中添加或删除节点,集群会自动进行数据的迁移。
分片
分片是一种数据库架构模式,它将数据分布在多个数据库或表中,以便可以并行地进行操作,从而提升性能。
[!TIP]
数据库架构模式-分片
数据库分片是一种将数据水平划分到多个数据库或表中的架构模式。在这种模式下,每个分片包含数据的一个子集,但从逻辑上看,所有分片一起形成一个统一的数据库。这种方法可以显著提升应用的可伸缩性和性能,特别是在处理大量数据和高并发请求的情况下。
分片的关键特点
水平划分:与垂直划分(按功能分)不同,分片通常是水平的,即按数据行进行划分。
透明性:对于应用程序来说,分片操作通常是透明的,应用不需要知道数据存储在哪个分片上。
可伸缩性:通过增加更多的分片,可以轻松扩展数据库的存储能力和处理能力。
分片策略
实现分片有多种策略,其中包括:范围分片:数据根据某个键的范围被分配到不同的分片上。例如,用户ID从1到1000的数据存储在分片A上,1001到2000的数据存储在分片B上。
哈希分片:使用哈希函数将数据键转换为哈希值,然后根据哈希值将数据分配到不同的分片上。这种方法可以均匀地分布数据。
目录分片:使用一个中央目录来维护键与分片之间的映射关系。这种方法在某些情况下可以提供更灵活的数据访问。分片的挑战
尽管分片提供了显著的性能和可伸缩性优势,但它也带来了一些挑战:复杂性增加:管理多个分片比管理单一数据库复杂得多。
跨分片操作:跨多个分片执行事务或联合查询可能会变得复杂且性能较差。
数据重新分配:随着数据量的增长或应用需求的变化,可能需要重新平衡分片中的数据,这个过程可能会很复杂且耗时。
在Redis中,分片通常是手动进行的,需要开发者或管理员根据应用需求将数据分布到不同的Redis实例上。
- 手动管理:与Redis集群不同,分片通常需要开发者或系统管理员手动配置和管理。
- 客户端支持:实现分片可能需要客户端库的支持,以便知道如何将数据键映射到正确的Redis实例。
- 灵活性:虽然管理起来更复杂,但分片提供了更高的灵活性。开发者可以根据具体需求,自定义数据的分布方式和规则。
集群 vs 分片
- 自动化:集群提供了比手动分片更多的自动化特性,如自动数据迁移和故障转移。
- 管理复杂度:集群减少了管理的复杂度,因为节点间的协调是自动进行的;而分片通常需要更多的手动配置和管理。
- 灵活性:手动分片提供了更高的灵活性,允许开发者根据具体应用需求定制数据分布策略。
Redis的事务
Redis通过MULTI、EXEC、DISCARD和WATCH命令来支持事务。事务允许将多个命令打包,然后通过一次EXEC命令原子性地执行。
-
开始事务:使用MULTI命令开始一个事务。
-
命令入队:在MULTI和EXEC之间的所有命令都不会立即执行,而是被放入一个队列中。
-
执行事务:使用EXEC命令执行所有队列中的命令。
-
取消事务:如果在执行过程中需要取消事务,可以使用DISCARD命令。
-
乐观锁:WATCH命令可以用来实现乐观锁,监视一个或多个key,如果在执行事务之前这些key的值被改变了,事务将被取消。
redis分布式事务实现
实现分布式事务并确保最终一致性是分布式系统设计中的一项挑战。
当下游有两个使用Redis的数据库需要更新时,可以采用以下策略来自行实现最终一致性:
-
两阶段提交(2PC)
两阶段提交是分布式事务中常用的一种协议,它包括准备阶段和提交阶段
- 准备阶段:事务协调者询问所有参与者是否准备好提交事务。如果所有参与者都回答准备好了,事务协调者进入下一阶段。
- 提交阶段:事务协调者指示所有参与者提交事务。
这种方法可以确保所有参与者要么都提交事务,要么都不提交,但它有可能导致资源锁定时间过长以及协调者单点故障问题。
-
最终一致性(BASE)
基于BASE理论的最终一致性模型是另一种实现分布式事务的方法。它强调系统在一定时间内达到一致状态,而非立即一致。实现方法包括
-
异步消息队列:使用消息队列异步通知各个服务进行数据更新,通过重试机制确保消息最终被成功处理。
-
补偿事务:如果某个操作失败,执行一个补偿操作来撤销前面的操作,确保数据的一致性。
-
TCC(Try-Confirm-Cancel):将每个操作分为尝试(Try)、确认(Confirm)和取消(Cancel)三个阶段。
-
首先尝试执行操作,确认所有操作都可以成功后再确认提交,如果有任何一个操作失败,则执行取消操作回滚之前的尝试。
- 使用分布式事务框架
考虑使用支持最终一致性的分布式事务框架,如Seata、Saga等。这些框架提供了成熟的解决方案来处理分布式事务,包括但不限于TCC、SAGA等模式,可以大大简化开发工作。
实施建议
- 在设计分布式事务时,首先考虑业务场景是否真的需要强一致性。如果业务可以容忍短暂的数据不一致,那么采用最终一致性模型可能是更好的选择。
- 明确每个阶段的回滚策略和补偿机制,确保在任何异常情况下都能保持数据的一致性。
- 考虑使用成熟的分布式事务框架来简化开发和维护工作。
Redis执行Lua脚本保证原子性的原因
Redis是单线程的,这意味着它一次只能执行一个命令。当执行Lua脚本时,整个脚本作为一个单独的命令执行,不会被其他Redis命令打断。因此,Lua脚本在执行过程中保持原子性,直到脚本完成执行,Redis才会执行其他命令。
Redis的持久化方式
Redis提供了两种主要的持久化方式:RDB(快照)和AOF(追加文件)。
- RDB:在指定的时间间隔内生成内存数据的快照并保存到磁盘上。这种方式适合大规模数据恢复。
- AOF:记录每个写操作命令,并追加到文件中。Redis重启时会重新执行这些命令来恢复数据。AOF提供了更好的数据安全性,但可能导致文件很大。
AOF持久化时的优化策略
为了优化AOF文件的大小,Redis提供了AOF重写机制。当AOF文件达到一定大小(可以配置),Redis可以启动一个子进程来创建一个新的AOF文件。这个过程称为AOF重写。新的AOF文件只包含达到当前数据状态所需的最少命令集。完成后,Redis切换到新的AOF文件,并且旧的AOF文件被删除。这样不仅减少了磁盘空间的占用,还能加快Redis启动时的数据恢复速度。
Redis缓存问题及解决方案
[!NOTE]
key
在缓存系统中,“key"一般指的是用于唯一标识存储在缓存中数据项的字符串。缓存系统,如Redis,通常是基于键值对(key-value pair)的数据结构,其中"key"用于查找对应的"value”。例如,在一个缓存用户信息的场景中,"key"可能是用户的ID或用户名,而"value"则是该用户的详细信息。在讨论缓存击穿、缓存雪崩和缓存穿透时,提到的"key"就是指这样的标识符。
-
缓存击穿:热点key突然失效,导致大量请求直接打到数据库。
缓存击穿是指一个热点的key在缓存中突然失效(过期),而此时恰好有大量的请求查询这个key。由于缓存失效,这些请求会直接打到后端数据库,造成数据库压力突然增大,可能会导致数据库短时间内无法处理这么多请求而崩溃。缓存击穿的关键点在于查询的是热点数据,即查询频率高的数据
- 解决方案:
- 设置热点key永不过期。
- 使用互斥锁或分布式锁确保当缓存失效时,只有一个请求去数据库查询数据并回填到缓存,其他请求等待缓存回填后再访问缓存。
- 解决方案:
-
缓存穿透:查询不存在的key,导致请求直接访问数据库。
缓存穿透是指查询不存在的数据。由于这部分数据不存在,每次查询都会穿过缓存直接打到数据库上,如果有大量此类查询,同样会给数据库带来不小的压力。
- 解决方案
- 对查询结果为空的情况也进行缓存,比如可以缓存一个空对象或特定的值,设置较短的过期时间。
- 使用布隆过滤器拦截不存在的查询,在请求查询数据之前先通过布隆过滤器检查数据是否存在。如果布隆过滤器判断数据不存在,则直接返回,不再查询数据库。
- 解决方案
-
缓存雪崩:大量key同时失效,导致请求急剧增加,压垮数据库。
缓存雪崩是指在某一个时间段,由于大量的缓存key同时过期,或者缓存服务宕机,导致所有的请求都落到了数据库上,造成数据库压力骤增甚至崩溃。与缓存击穿不同,缓存雪崩通常影响面更广,不仅限于某个热点key。
- 解决方案
- 为key设置不同的过期时间。
- 使用高可用架构如使用集群、主从复制、哨兵监控。
- 限流降级策略,避免数据库压力过大。
- 解决方案
内存满了继续写入会发生什么
当Redis内存满了继续写入时,Redis会根据配置的淘汰策略来删除一部分key,以便为新写入的数据腾出空间。
常见的淘汰策略包括:
- LRU算法删除数据:最近最少使用的数据被淘汰。
- 删除带有过期时间的键:优先删除即将过期的key。
- 随机删除键:随机删除一些key。
- 不删除任何键:当内存不足时,返回错误,不再接受新的写入命令。
Redis清理过期key的时机
Redis清理过期key主要有两种方式:
-
被动删除:当访问一个已经过期的key时,Redis会检查并立即删除该key。
-
主动删除:Redis会定期随机检查一些key,发现过期的key就删除它们。
Redis不会为了删除过期key而新启动一个进程。
这些操作都是在Redis主线程中完成的,以避免多线程带来的复杂性和性能问题。
查找命令——keys和scan
KEYS命令
KEYS命令用于查找所有符合给定模式的key。比如,KEYS name会返回所有包含"name"字符串的key。这个命令非常简单直接,但在包含大量key的生产环境中使用时有很大的性能风险。当你执行KEYS命令时,Redis会锁定数据库直到查找操作完成,这期间不会处理任何其他命令。这意味着,如果你的Redis数据库中有大量的key,并且你使用了一个宽泛的匹配模式,KEYS命令可能会造成长时间的阻塞,影响数据库的响应时间。
SCAN命令
为了解决KEYS命令可能引起的性能问题,Redis引入了SCAN命令。SCAN命令是以一种对服务器友好的方式来渐进式地遍历数据库的key。它返回一个游标(cursor),这个游标随后可以被用来作为下一次调用SCAN命令的起点。通过这种方式,SCAN命令可以分批次逐步返回所有匹配的key,而不会阻塞服务器。
SCAN命令主要参数
-
cursor:游标的起始点,初次调用时通常使用0。
-
MATCH pattern(可选):允许你指定一个模式,只返回匹配该模式的key。
-
COUNT count(可选):虽然SCAN命令每次调用返回的结果数量不固定,但你可以通过这个参数建议Redis每次返回的大约结果数目。
注意:虽然SCAN命令不会阻塞服务器,但由于它是分批次返回结果,可能需要多次调用才能遍历所有key。此外,由于Redis的数据结构和内部变化,同一个key可能会在一次遍历中被返回多次,所以客户端需要处理可能的重复结果。
总结
[!CAUTION]
造成阻塞的主要原因是KEYS命令需要扫描整个数据库来查找匹配特定模式的所有key。如果数据库中的key数量非常多,而且使用的模式比较宽泛(例如*匹配所有key),那么KEYS命令就需要遍历数据库中的每一个key来检查是否匹配,这会消耗大量的时间和计算资源。
在这个过程中,Redis是单线程的,意味着在执行KEYS命令期间,Redis无法执行其他任何命令。因此,如果KEYS命令执行时间过长,就会导致Redis服务器在这段时间内无法响应其他客户端的请求,造成阻塞。
相比之下,SCAN命令通过分批次返回结果来避免长时间的阻塞。它允许服务器在处理大量key的同时,还能够处理来自其他客户端的请求。尽管SCAN命令可能需要多次调用才能遍历整个数据库,但它通过避免一次性处理大量数据来减少了对服务器性能的影响。
使用KEYS命令可以快速查找匹配特定模式的所有key,但在包含大量key的生产环境中可能会引起性能问题。
SCAN命令提供了一种更安全、更高效地方式来遍历key,特别是在生产环境中。它通过分批次返回结果,避免了长时间的阻塞,但需要客户端处理可能的重复结果。
大量长期有效的key处理
如果需要扫描所有key,并对符合特定条件的key进行删除,可以使用scan命令配合删除命令(如del)进行处理。scan命令可以分批次遍历所有的key,避免一次性加载所有key导致的性能问题。
scan与keys命令的区别
keys命令会阻塞Redis服务器,影响其它操作,因此在生产环境中应避免使用。
scan命令则提供了一种渐进式遍历键空间的方式,它每次只返回一小部分元素,并且可以通过游标进行分批处理,从而避免了长时间的阻塞。但需要注意的是,scan命令可能会返回重复的元素,需要客户端自行处理去重。
令配合删除命令(如del)进行处理。scan命令可以分批次遍历所有的key,避免一次性加载所有key导致的性能问题。
scan与keys命令的区别
keys命令会阻塞Redis服务器,影响其它操作,因此在生产环境中应避免使用。
scan命令则提供了一种渐进式遍历键空间的方式,它每次只返回一小部分元素,并且可以通过游标进行分批处理,从而避免了长时间的阻塞。但需要注意的是,scan命令可能会返回重复的元素,需要客户端自行处理去重。