Redis知识点总结--4

本文详细解析Redis Cluster的架构、数据分布、节点通信机制,涵盖节点伸缩、客户端请求路由、缓存设计与优化策略,包括无底洞、雪崩和热点key处理。深入探讨了布隆过滤器、永不过期策略等缓存管理技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis知识点总结–1
Redis知识点总结–2
Redis知识点总结–3

8.集群

Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。之前、Redis分布式方案一般有两种:
① 客户端分区方案,优点是分区逻辑可控,缺点是需要自己处理数据路由、高可用、故障转移等问题;
② 代理方案,优点是简化客户端分布式逻辑和升级维护便利,缺点是加重架构部署复杂度和性能损耗。

8.1 数据分布
8.1.1 数据分布理论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。
在这里插入图片描述

需要关注的是数据分区规则。常见的分区规则有哈希分区和顺序分区两种。哈希分区特点是离散度好、数据分布业务无关、无法顺序访问;顺序分区特点是离散度易倾斜、数据分布业务相关、可顺序访问。Redis Cluster选择的是哈希分区,常见的哈希分区规则有几种,下面分别介绍:
1) 节点取余分区
使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。这种方案存在一个问题:当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。
这种方式的突出优点是简单性,常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区,比如划分为512或1024张表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数据库中。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。
在这里插入图片描述

2) 一致性哈希分区
一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个token,范围一般在0—232,这些token构成一个哈希环。数据读写执行节点查找时,先根据key计算hsah值,然后顺时针找到第一个大于等于该哈希值的token节点。
在这里插入图片描述

这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。但一致性哈希分区存在几个问题:
① 加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景;
② 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量数据节点的分布式方案;
③ 普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。
3) 虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如Redis Cluster槽范围是0—16383.槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,如下图所示。
在这里插入图片描述

当前集群有5个节点,每个节点平均大约负责3276个槽。由于采用高质量的哈希函数,每个槽所映射的数据通常比较均匀,将数据平均划分到5个节点进行数据分区。Redis Cluster就是采用虚拟槽分区。

8.1.2 Redis数据分区

Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0—16383整数槽内,计算公式:slot=CRC16(key)&16383.每个节点负责维护一部分槽以及槽所映射的键值数据。
Redis虚拟槽分区的特点:
① 解耦数据和节点之间的关系,简化了节点扩容和收缩难度;
② 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据;
③ 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。

8.1.3 集群功能限制

Redis集群相对单机在功能上存在一些限制,需要开发人员提前了解,在使用时做好规避。限制如下:
① key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的key执行批量操作。对于映射为不同slot值的key由于执行mget、mset等操作可能存在于多个节点上因此不被支持;
② key事务操作支持有限。同理只支持多key在同一个节点上的事务操作,当多个key分布在不同的节点上时无法使用事务功能;
③ key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等映射到不同的节点;
④ 不支持多数据库空间。单机下的Redis可以支持16个数据库,集群模式下只能使用一个数据库空间,即db0;
⑤ 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。

8.1.4 搭建集群

搭建集群工作需要以下三个步骤:
① 准备节点
② 节点握手
③ 分配槽
以上三个步骤所针对的都是主节点,作为一个完整的集群,每个负责处理槽的节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。集群模式下,Redis节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关数据。使用cluster replicate {nodeID}命令让一个节点成为从节点,其中命令执行必须在对应的从节点上执行。

8.2 节点通信
8.2.1 通信流程

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。常见的元数据维护方式分为:集中式和P2P方式。Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。
通信过程说明:
① 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000;
② 每个节点在固定周期内通过特定规则选择几个节点发送ping消息;
③ 接收到ping消息的节点用pong消息作为响应。
集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等时间发生时,通过不断ping/pong消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。

8.2.2 Gossip

Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息。常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等。
① meet消息:用于通知新节点加入。消息发送者通知接受者加入到当前集群,meet信息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换;
② ping消息:集群内交换最频繁的信息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping信息发送封装了自身节点和部分其他节点的状态数据;
③ pong信息:当接收到ping、meet信息时,作为响应信息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据,节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新;
④ fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

8.2.3 节点选择

虽然Gossip协议的信息交换机制具有天然的分布式特性,但它是有成本的。由于内部需要频繁地进行节点信息交换,而ping/pong消息会携带当前节点和部分其他节点的状态数据,势必会加重带宽和计算的负担。Redis集群内节点通信采用固定频率(定时任务每秒执行10次)。因此节点每次选择需要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率,从而影响故障判定、新节点发现等需求的速度。因此Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销,通信节点选择规则如下图所示:
在这里插入图片描述

根据通信节点选择的流程可以看出消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

8.3 集群伸缩
8.3.1 伸缩原理

Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。其中原理可抽象为槽和对应数据在不同节点之间灵活移动。

8.3.2 扩容集群

Redis集群扩容操作如下:
① 准备新节点
② 加入集群
③ 迁移槽和数据
第三步中,槽是Redis集群管理数据的基本单位,首先需要为新节点定制槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相识数量的槽,从而保证各节点的数据均匀。(数据迁移过程是逐个槽进行的,槽在迁移过程中集群可以正常提供读写服务)

8.3.3 收缩集群

收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点。流程如下:
① 首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性;
② 当下线节点不在负责槽或者本身是从节点(只有主节点负责槽)时,就可以通知集群内其他节点忘记下线节点,当所有节点忘记该节点后可以正常关闭。

8.4 客户端请求路由
8.4.1 请求重定向

在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误(包含键所在节点信息),通知客户端请求正确的节点。这个过程称为MOVED重定向。(节点对于不属于它的键命令只回复重定向响应,并不负责转发)
根据MOVED重定向机制,客户端可以随机连接到集群内任一Redis获取键所在节点,这种客户端又叫傀儡客户端,它的优点是代码实现简单,对客户端协议影响较小,只需要根据重定向信息再次发送请求即可。但它的弊端很明显,每次执行键命令前都要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销,这不是Redis集群高效的使用方式。正因为如此通常集群客户端都采用另外一种实现:Smart(智能)客户端。

8.4.2 Smart(智能)客户端

Smart(智能)客户端通过在内部维护slot->node的映射关系,本地就可以实现键到节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助Smart客户端更新slot->node映射。
键命令执行流程:
① 计算slot并根据slots缓存获取目标节点连接,发送命令;
② 如果出现连接错误,使用随机连接重新执行键命令,每次命令重试对redirections参数减1;
③ 捕获到MOVED重定向错误,利用cluster slots命令更新slots缓存(renew SlotCache方法);
④ 重复执行①②③,直到命令执行成功,或者当redirections<=0时抛出JedisClusterMaxRedirectionsException异常。

8.4.3 ASK重定向

客户端ASK重定向流程:
Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程中,客户端需要做到智能识别,保证键命令可正常执行。例如当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分数据在目标节点。
当出现上述情况时,客户端命令执行流程将发生变化:
① 客户端根据本地slots缓存发送命令到源节点,如果存在键对象则直接执行并返回结果给客户端;
② 如果键对象不存在,则可能存在于目标节点,这时源节点会回复ASK重定向异常。格式如下:(error) ASK {slot} {targetIP}:{targetPort}。
③ 客户端从ASK重定向异常中提取出目标节点信息,发送asking命令到目标节点打开客户端连接标识,再执行键命令。如果存在则执行,不存在则返回不存在信息。
ASK与MOVED虽然都是客户端的重定向控制,但是有着本质区别。ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slots缓存。

配置纪元:
配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元(clusterNode.configEpoch)标识当前主节点的版本,所有主节点的配置纪元都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配置纪元(clusterState.currentEpoch),用于记录集群内所有主节点配置纪元的最大版本,执行cluster info命令可以查看配置纪元。
配置纪元的主要作用:
① 标识集群内每个主节点的不同版本和当前集群最大的版本;
② 每次集群发生重要事件时,这里的重要事件指出现新的主节点(新加入的或者由从节点转换而来),从节点竞争选举。都会递增集群全局的配置纪元并赋值给相关主节点用于记录这一关键时间;
③ 主节点具有更大的配置纪元代表了更新的集群状态,因此当节点间进行ping/pong消息交换时,如出现slots等关键信息不一致时,以配置纪元更大的一方为准,防止过时消息状态污染集群。
配置纪元会随ping/pong消息在集群内传播,当发送方与接收方都是主节点且配置纪元相等时代表出现了冲突,nodeId更大的一方会递增全局配置纪元并赋值给当前节点来区分冲突。
配置纪元的应用场景:
① 新节点加入;
② 槽节点映射冲突检查;
③ 从节点投票选举冲突检查。

集群伸缩通过在节点之间移动槽和相关数据实现。扩容时根据槽迁移计划把槽从源节点迁移到目标节点,源节点负责的槽相比之前变少从而达到集群扩容的目的;收缩时如果下线的节点有负责的槽需要迁移到其他节点,在通过cluster forget命令让集群内其他节点忘记被下线节点。

使用smart客户端操作集群达到通信效率最大化,客户端内部负责计算维护键->槽->节点的映射,用于快速定位键命令到目标节点。节点收到键命令时会判断相关的槽是否由自身节点负责,如果不是则返回重定向信息。重定向分为MOVED和ASK,ASK说明集群正在进行槽数据迁移(客户端无法知道什么时候迁移完成,因此只能临时重定向),客户端只在本次请求中做临时重定向,不会更新本地槽缓存。MOVED重定向说明槽已经明确分派到另外一个节点,客户端需要更新槽节点缓存。

9.缓存设计

9.1 缓存的收益和成本

在这里插入图片描述

上图中左侧为客户端直接调用存储层的架构,右侧为比较典型的缓存层+存储层架构。
收益如下:
① 加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍,通过缓存的使用可以有效地加速读写,优化用户体验;
② 降低后端负载:帮助后端减少访问量和复杂计算,在很大程度降低了后端的负载。
成本如下:
① 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关;
② 代码维护成本;
③ 运维成本;
缓存的使用场景基本包含如下两种:
① 开销大的复杂计算:以MySQL为例,一些复杂的操作或者计算(例如大量联表操作、一些分组操作),如果不加缓存,不但无法满足高并发量,同时也会给MySQL带来巨大的负担;
② 加速请求响应:即使查询单条后端数据足够快,那么依然可以使用缓存,以Redis为例,每秒可以完成数万次读写,并且提供的批量操作可以优化整个IO链的响应时间。

9.2 缓存更新策略

缓存中的数据通常都是有生命周期的,需要在指定时间后被删除或更新,这样可以保证缓存空间在一个可控的范围。但是缓存中的数据会和数据源中的真实数据有一段时间窗口的不一致,需要利用某些策略进行跟新。
1) LRU/LFU/FIFO算法剔除
① 使用场景。剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。
② 一致性。要清理哪些数据是由具体算法决定,开发人员只能决定使用哪种算法,所以数据一致性是最差的。
③ 维护成本。算法不需要开发人员自己来实现,通常只需要配置最大maxmemory和对应的策略即可。开发人员只需要知道每种算法的含义,选择适合自己的算法即可。
2) 超时剔除
① 使用场景。超时剔除通常给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。
② 一致性。一段时间窗口内(取决于过期时间长短)存在一致性问题,即缓存数据和真实数据源的数据不一致。
③ 维护成本。维护成本不是很高,只需要设置expire过期时间即可,当然前提是应用方允许这段时间可能发生数据不一致。
3) 主动更新
① 使用场景。应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消息系统或者其他方式通知缓存更新。
② 一致性。一致性最高,但如果主动更新发生了问题,那么这条数据很可能很长时间不会更新,所以建议结合超时剔除一起使用效果会更好。
③ 维护成本。维护成本比较高,开发者需要自己来完成更新,并保证更新操作的正确性。

9.3 穿透优化

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错考虑,如果从存储层查不到数据则不写入缓存层:
在这里插入图片描述

整个过程分为三步:
① 缓存层不命中;
② 存储层不命中,不将结果写回缓存;
③ 返回空结果。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空明中,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空明中。下面将讨论解决缓存穿透问题。

9.3.1 缓存空对象

在这里插入图片描述

如上图所示,当第②步存储层不命中后,任然将空对象保留到缓存层,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
缓存空对象会有两个问题:第一,控制作了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清楚掉存储层中的空对象。

9.3.2 布隆过滤器拦截

在这里插入图片描述

如上图所示,在访问层和存储层之前,将存在的key用布隆过滤器提前保存起来,做一层拦截。
1) 布隆过滤器是什么
可以把布隆过滤器理解为一个不怎么精确的set结构,当使用它的contains方法判断某个对象是否存在时,它可能误判。但是布隆过滤器也不是特别不精确,只要参数设置得合理,它的精确度可以控制得相对足够精确,只会有小小的误判概率。当布隆过滤器说某个值存在时,这个值可能不存在;当它说某个值不存在时,那就肯定不存在。
2) 布隆过滤器原理
每个布隆过滤器对应到Redis的数据结构里面就是一个大型的位数据和几个不一样的无偏hash函数,如下图所示,f、g、h就是这样的hash函数。所谓无偏就是能够把元素的hash值算得比较均匀,让元素被hash映射到位数组中的位置比较随机。
在这里插入图片描述

向布隆过滤器中添加key时,会使用多个hsah函数对key进行hash,算得一个整数索引值,然后对位数组长度进行取模运算得到一个位置,每个hash函数都会算得一个不同的位置。再把位数组的这几个位置都置为1,就完成了add操作。
向布隆过滤器询问key是否存在时,跟add一样,也会把hash的几个位置都运算出来,看看位数组中这几个位置是否都为1,只要有一个为0,那么说明布隆过滤器中这个key不存在。如果这几个位置都是1,并不能说明这个key就一定存在,只是极有可能存在,因为这些位置为1可能是因为其他的key存在所致。如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。

9.4 无底洞优化

2010年,Facebook的Memcache节点已经达到了3000个,承载着TB级别的缓存数据。但开发和运维人员发现了一个问题,为了满足业务要求添加了大量新Memcache节点,但是发现性能不但没有好转反而下降了,当时将这种现象称为缓存的“无底洞”现象。
那么为什么会产生这种现象呢,通常来说添加节点使得Memcache集群性能应该更强了,但事实并非如此。键值数据库由于通常采用哈希函数将key映射到各节点上,造成key的分布与业务无关,但是由于数据量和访问量的持续增加,造成需要添加大量节点做水平扩容,导致键值分布到更多的节点上,所以无论是Memcache还是Redis的分布式,批量操作通常需要从不同节点上获取,相比于单机批量操作只涉及依一次网络操作,分布式批量操作会涉及多次网络时间。
解决方案:
① 串行命令
② 串行IO
③ 并行IO
④ hash_tag

9.5 雪崩优化

由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
1) 保存缓存层服务高可用。
2) 依赖隔离组件为后端限流并降级。无论是缓存层还是存储层都会出现出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞在这个资源上,造成整个系统不可用。降级机制在高并发系统中是非常普遍的,在实际项目中,我们需要对重要资源(例如Redis、MySQL、Hbase、外部接口)都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。
3) 提前演练。

9.6 热点key重建优化

开发人员使用“缓存+过去时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但有两个问题如果同时出现,可能就会对应用造成致命危害:
① 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大;
② 重建缓存不能在短期完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程重建缓存,造成后端负载加大,甚至可能会让应用奔溃。
要解决这个问题也不复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:
① 减少重建缓存的次数;
② 数据尽可能一致;
③ 较少的潜在危险。
1) 互斥锁
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完成,重新从缓存获取数据即可。
2) 永远不过期
包含两层意思:
① 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期;
② 从功能层面看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
作为一个并发量较大的应用,在使用缓存时有三个目标:第一,加快用户访问速度,提高用户体验。第二,降低后端负载,减少潜在的风险,保证系统平稳。第三,保证数据“尽可能”及时更新。
两种方案对比分析:
互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程中出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增加。

知识点参考:
《Redis开发与运维》 付磊 张益军
《Redis深度历险》 钱文品

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值