Redis 支持多种数据类型:
字符串
示例:存储用户信息
// 假设我们使用 redis-plus-plus 客户端库
auto redis = Redis("tcp://127.0.0.1:6379");
redis.set("user:1000", "{'name': 'John Doe', 'email': 'john.doe@example.com'}");
std::string userInfo = redis.get("user:1000").value_or("");
列表(List)
示例:任务队列
// 将任务添加到队列尾部
redis.lpush("task_queue", "Task 1");
redis.rpop("task_queue"); // 从队列头部取出并删除任务
集合(Set)
示例:标签系统
// 给文章添加标签
redis.sadd("article:12345:tags", {"tech", "news"});
// 获取所有标签
auto tags = redis.smembers("article:12345:tags");
有序集合(Sorted Set)
// 添加玩家分数
redis.zadd("leaderboard", {{ "player:1", 850 }, { "player:2", 920 }});
// 获取前五名玩家
auto topPlayers = redis.zrevrange("leaderboard", 0, 4, true);
for (const auto &player : topPlayers) {
std::cout << "Player: " << player.member << ", Score: " << player.score << std::endl;
}
哈希(Hash)
示例:存储用户详细信息
// 存储用户详情
redis.hset("user:1000", {{"name", "Jane Doe"}, {"age", "30"}, {"email", "jane.doe@example.com"}});
// 获取用户的年龄
auto age = redis.hget("user:1000", "age").value_or("");
位图(Bitmaps)
示例:签到统计
// 记录用户第i天是否签到
redis.setbit("user:1000:sign_in", 1, 1); // 第一天签到
bool signedInToday = redis.getbit("user:1000:sign_in", 1); // 检查第一天是否签到
HyperLogLog
示例:独立访客统计
// 增加一个独立访客
redis.pfadd("unique_visitors", {"user1", "user2", "user3"});
// 估算独立访客数量
long long estimatedCount = redis.pfcount("unique_visitors");
地理空间(Geospatial)
// 添加一些地点
redis.geoadd("locations", {{-122.41, 37.77, "San Francisco"}, {-118.24, 34.05, "Los Angeles"}});
// 查找距离旧金山100公里内的地点
auto locations = redis.georadius("locations", -122.41, 37.77, 100, "km");
for (const auto &loc : locations) {
std::cout << loc.member << std::endl;
}
redis和memcached的区别
Redis 和 Memcached 都是高性能的内存键值存储系统,常被用作缓存解决方案来提升应用性能。尽管它们有相似的应用场景,但在设计理念、功能特性等方面存在显著差异。以下是 Redis 和 Memcached 的主要区别:
- 数据类型支持
Redis:支持丰富的数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)、位图(Bitmaps)、HyperLogLog、地理空间(Geospatial)等。这使得 Redis 能够处理更复杂的任务,如排序、范围查询等。
Memcached:仅支持简单的键值对,其中键和值都是字节数组。它的设计目标是提供一个快速的缓存层,因此没有复杂的数据结构支持。 - 持久化
Redis:支持持久化选项,包括 RDB 快照和 AOF(Append Only File)。这意味着 Redis 可以在重启后恢复之前的数据状态。
Memcached:不支持持久化,所有数据都存储在内存中,如果服务器重启或崩溃,所有缓存的数据都会丢失。 - 内存管理
Redis:具有更复杂的内存管理机制,能够根据需要自动回收内存,并且可以配置最大内存使用限制以及淘汰策略(如 LRU 等)。
Memcached:采用 slab 分配算法来分配内存,一旦分配就不能改变大小,可能导致内存碎片问题。 - 复杂操作
Redis:提供了诸如事务、发布/订阅、Lua 脚本执行、分布式锁等多种高级功能,允许用户执行更为复杂的操作。
Memcached:主要是为简单读写操作优化的,不支持事务、脚本执行等高级功能。 - 主从复制与集群
Redis:支持主从复制和哨兵(Sentinel)机制,用于实现高可用性;还支持集群模式,允许水平扩展。
Memcached:本身不具备主从复制和集群功能,但可以通过客户端实现一致性哈希等技术来构建分布式缓存。 - 性能
Redis:由于其额外的功能特性,可能在某些情况下比 Memcached 稍慢,但对于大多数应用场景而言,这种差异是可以忽略的。
Memcached:专注于高速缓存,对于简单的键值对操作非常高效。 - 使用场景
Redis:适合需要持久化、复杂数据结构、消息队列、计数器等高级功能的场景。
Memcached:适用于那些只需要简单键值对缓存、对持久化没有要求的高性能读写场景。
综上所述,选择 Redis 还是 Memcached 应基于具体的需求和使用场景。如果你的应用需要复杂的操作、持久化或者高级功能,那么 Redis 是更好的选择。而如果你的应用只需简单的缓存机制,追求极致的速度,那么 Memcached 可能更适合。
Redis 提供的两种持久化机制
RDB 快照 (Redis Database Backup)
RDB 是一种快照持久化方式,它会在指定的时间间隔内将内存中的数据集以二进制形式写入磁盘上的一个文件中,通常这个文件名为 dump.rdb。
当 Redis 需要恢复数据时,它会加载最近一次生成的 RDB 文件来恢复数据。
可以通过修改 Redis 的配置文件 (redis.conf) 来设置触发快照的条件,例如:
save 900 1 # 在过去900秒内至少有1个键发生变化则触发快照
save 300 10 # 在过去300秒内至少有10个键发生变化则触发快照
save 60 10000 # 在过去60秒内至少有10000个键发生变化则触发快照
优点:
性能影响小:由于 RDB 是在特定时间点进行的数据快照,因此对性能的影响相对较小。
恢复速度快:因为 RDB 文件是以紧凑的二进制格式存储的,所以读取速度较快,适合用于灾难恢复。
缺点:
数据丢失风险:如果 Redis 发生故障,自上次快照以来的所有数据更新都将丢失。
占用更多内存:创建快照期间可能需要额外的内存来执行复制操作。
AOF (Append Only File)
描述:
AOF 持久化记录服务器接收到的每一个写操作,在 Redis 重启时会重新执行这些命令来重建原始数据集。
默认情况下,AOF 文件每秒钟同步一次(fsync),也可以配置为每次写操作后立即同步,但这会影响性能。
配置:
启用 AOF 需要在 redis.conf 中设置
appendonly yes
还可以调整同步策略,比如:
appendfsync everysec # 每秒同步一次
appendfsync always # 每次写操作后同步
appendfsync no # 不主动同步,依赖操作系统
优点:
更高的数据安全性:因为所有写操作都会被记录下来,所以在发生崩溃时最多只会丢失一秒钟的数据。
灵活性:可以通过不同的 fsync 策略平衡性能与数据安全之间的关系。
缺点:
文件体积较大:随着时间推移,AOF 文件可能会变得非常大,尽管 Redis 支持自动重写(rewrite)功能来压缩文件大小。
恢复速度较慢:由于需要重新执行所有的命令来恢复数据,因此启动时间可能比使用 RDB 更长。
Redis 对于过期键的删除采用了两种主要策略:
惰性删除(Lazy Expiration)和定期删除(Active Expiration)。这两种策略相互补充,旨在保证在不过多影响性能的前提下,有效地管理和清理过期键。
- 惰性删除(Lazy Expiration)
描述:
当访问一个键时,Redis 会检查该键是否已经过期。如果键已过期,则 Redis 会立即删除该键,并返回不存在给客户端。
这种方式确保了即使键已经过期,它也不会占用内存,直到下一次被访问。
优点:
非常简单且高效,因为只有在键被访问时才会进行过期检查,避免了不必要的资源消耗。
缺点:
如果某个键过期后长时间未被访问,那么它仍然可能占据着内存空间,直到下一次访问时才被删除。
- 定期删除(Active Expiration)
描述:
Redis 会周期性地随机检查一些键,并对这些键执行过期检查。通过这种方式,Redis 能够主动地清除那些已经过期但尚未被访问到的键。
Redis 实现了一个定时任务,默认情况下每秒运行10次,每次从设置了过期时间的键中随机抽取一定数量的键进行检查并删除那些已经过期的键。
为了防止这个过程消耗过多的CPU资源,Redis 使用了一些限制条件来控制其执行频率和每次处理的数量。
优点:
可以有效减少因惰性删除导致的过期键长期占用内存的问题。
有助于保持内存使用的健康状态,避免大量过期键积累。
缺点:
如果设置不当,可能会增加系统的负载。然而,由于Redis对其进行了精心的设计,通常不会对性能造成显著影响。
如何选择
这两种策略并不是互相排斥的,而是共同作用来管理过期键。惰性删除保证了每个访问的键都是有效的,而定期删除则帮助清理那些虽然过期但尚未被访问的键。这样的组合使得 Redis 在大多数情况下能够很好地平衡性能与内存使用效率。
此外,Redis 还允许用户配置具体的淘汰策略(如 volatile-lru、allkeys-lru 等),当达到设定的最大内存限制时,Redis 将根据所选策略自动移除部分键值对,这也有助于控制内存使用量。不过,这些策略主要是针对内存压力管理而不是直接处理过期键的删除。
redis的回收策略
Redis 提供了几种不同的内存回收策略(Eviction Policies),这些策略决定了当 Redis 达到配置的最大内存限制时,如何选择并移除某些键以释放空间。Redis 的内存回收策略主要通过 maxmemory 和 maxmemory-policy 参数来控制。
配置最大内存
首先,你需要设置 Redis 实例允许使用的最大内存量。这可以通过 maxmemory 配置项完成:
maxmemory 256mb
此设置告诉 Redis 当使用的内存达到 256MB 时开始执行内存回收策略。
内存回收策略 (maxmemory-policy)
Redis 支持多种内存回收策略,可以通过 maxmemory-policy 参数进行配置。以下是几种常见的策略:
noeviction
默认策略。当内存达到上限时,任何写操作都会返回错误(除了读操作如 GET),不会尝试删除任何键。
allkeys-lru
从所有键中挑选最近最少使用的(Least Recently Used, LRU)键进行淘汰。适用于大多数需要缓存的应用场景,因为它倾向于保留最常用的数据。
volatile-lru
只针对设置了过期时间的键使用 LRU 算法进行淘汰。如果系统中没有或很少有过期键,则效果类似于 noeviction。
allkeys-random
随机选择键进行淘汰,不论其是否设置了过期时间。适合于数据重要性大致相同的情况。
volatile-random
随机选择那些已经设置了过期时间的键进行淘汰。适用于希望优先清理临时数据的场景。
volatile-ttl
选择即将过期的键优先淘汰。这有助于尽可能长时间地保持数据的有效性,特别适用于依赖 TTL 来管理缓存生命周期的应用。
allkeys-lfu (Redis 4.0+)
从所有键中挑选最不经常使用的(Least Frequently Used, LFU)键进行淘汰。比 LRU 更加智能,因为它考虑了键的访问频率。
volatile-lfu (Redis 4.0+)
类似于 allkeys-lfu,但仅应用于设置了过期时间的键。
如何选择合适的策略
选择哪种策略取决于你的应用需求:
如果你希望最大限度地利用缓存中的热门数据,那么 allkeys-lru 或 allkeys-lfu 是不错的选择。
如果你有大量设置了过期时间的键,并且希望通过自然过期机制来管理内存,可以考虑 volatile-lru 或 volatile-ttl。
对于那些对数据丢失敏感的应用,可能需要避免使用自动淘汰机制,或者至少确保关键数据不被误删,这时可以选择 noeviction 并手动处理内存超限问题。
redis的同步机制:
Redis 的主从复制、哨兵模式和集群模式虽然各自有不同的设计目标,但它们都涉及数据同步机制,以确保数据的一致性和高可用性。下面详细解释这三种模式与同步机制的关系,并通过具体例子来说明它们如何工作。
- 主从复制(Replication)
关系:主从复制是 Redis 最基础的同步机制,它通过异步的方式将主节点的数据变化同步到一个或多个从节点上。这种机制为 Redis 提供了读扩展能力和一定的故障恢复能力。
同步机制:
全量同步(Full Resynchronization):当一个新的从节点连接到主节点时,或者在某些错误情况下,会进行一次完整的数据同步,包括生成 RDB 文件并传输给从节点。
部分同步(Partial Resynchronization):如果主从之间的连接断开后重新建立,且满足一定条件(如复制积压缓冲区中存在所需数据),则可以只同步断连期间丢失的部分数据,而不是整个数据库。
例子:
假设你有一个电商网站,主节点负责处理所有写操作(如用户下单),而从节点用于处理读请求(如查看商品详情)。这样不仅可以分担主节点的压力,还能提高查询速度。
- 哨兵模式(Sentinel)
关系:哨兵模式基于主从复制之上,增加了自动故障检测和故障转移功能。当主节点出现故障时,哨兵系统能够自动将某个从节点提升为主节点,从而保证服务的连续性。
同步机制:
在正常运行期间,哨兵持续监控主从节点的状态。
一旦发现主节点不可用,哨兵将启动选举流程,选择一个新的主节点,并通知所有相关的从节点切换到新的主节点上。
例子:
继续上述电商网站的例子,如果你希望在主服务器发生故障时,系统能够自动恢复而不影响用户体验,那么你可以部署哨兵模式。例如,当主节点因硬件故障离线时,哨兵会自动将其中一个从节点升级为主节点,确保订单处理和其他关键业务不受影响。
- 集群模式(Cluster)
关系:集群模式不仅实现了数据的分布式存储,还内置了数据同步机制,以确保各分片之间的数据一致性。每个节点都管理着一部分数据及其副本,提高了系统的可扩展性和容错能力。
同步机制:
数据根据键的空间分区分布到不同的节点上。
每个节点都知道其他节点的信息,以及哪些键应该存储在哪一个节点上。
当需要进行数据迁移或添加新节点时,集群会自动调整数据分布。
例子:
假设你的电商网站规模扩大,单台服务器无法再承载所有的数据和请求。这时你可以使用 Redis Cluster 将数据分散到多个节点上。比如,可以根据用户ID对用户会话信息进行分片存储,使得不同用户的会话信息分布在不同的节点上,既提高了性能也增强了系统的扩展能力。
综合比较
主从复制:适用于需要增加读性能或提供简单备份方案的场景。它的同步机制相对简单,但不具备自动故障恢复的能力。
哨兵模式:在主从复制的基础上增加了自动故障检测和恢复功能,适合那些对服务连续性要求较高的应用。
集群模式:提供了更强大的扩展性和容错能力,特别适合大规模数据集和高并发访问的场景。它不仅解决了单点故障问题,还支持水平扩展。
实际应用场景示例
小型电商网站初期阶段:可能只需要一个主节点加一个从节点的配置,使用主从复制来分担读负载。此时,哨兵模式可用于确保在主节点失败时能快速恢复服务。
随着业务增长:如果流量增加导致单一主节点成为瓶颈,可以考虑引入哨兵模式,确保即使主节点出现问题也能迅速恢复正常运营。
大型电商平台:面对海量用户和复杂业务逻辑,可能会采用 Redis Cluster 来实现数据分片,同时结合哨兵模式保障高可用性。这样既能处理大量并发请求,又能灵活应对数据增长带来的挑战。
通过合理选择和组合这些模式,可以根据实际需求构建出既高效又可靠的 Redis 部署方案。
redis集群的原理
Redis 集群(Redis Cluster)是 Redis 官方提供的分布式解决方案,旨在解决单节点 Redis 在数据量大和高并发场景下的局限性。它通过数据分片、自动故障转移等机制实现了高可用性和可扩展性。以下是 Redis 集群的工作原理及其核心特性:
核心概念
- 数据分片(Sharding):
Redis 集群将整个键空间分成多个槽(slot),默认情况下共有 16384 个槽。
每个节点负责处理一部分槽中的数据。例如,在一个包含 3 个主节点的集群中,每个节点可能负责大约 5461 个槽。
- 主从复制:
每个主节点都有一个或多个从节点作为备份。当主节点发生故障时,其中一个从节点可以被提升为主节点,以保证服务的连续性。
- 一致性哈希:
键值对根据哈希算法分配到不同的槽中。具体来说,Redis 使用 CRC16 算法计算键的哈希值,并对 16384 取模来确定该键应位于哪个槽中。
- 工作流程
数据分布与访问
当客户端请求到来时,首先需要知道要操作的数据位于哪个节点上。为此,客户端要么事先知道键所在的槽,要么查询集群得知该信息。
对于写操作,如果目标槽位于当前主节点,则直接执行;否则,重定向至正确的节点。
读操作则可以根据需求选择是否从主节点或从节点读取数据。
自动故障检测与恢复
集群内的节点会定期交换消息(PING/PONG),以检查其他节点的状态。
如果某个主节点被认为不可用,集群会触发故障转移过程:从其副本中选出一个新的主节点,并更新集群状态,使得其他节点知晓新的配置。
特性与优势
高可用性:
通过主从复制和自动故障转移,确保即使某些节点失败,服务也能继续运行。
可扩展性:
支持在线添加或移除节点,动态调整数据分布,适应不断变化的工作负载。
分区容错性:
虽然 Redis 集群不支持强一致性(遵循最终一致性模型),但在网络分区情况下仍能提供一定程度的服务。
- 实际例子
假设我们有一个由 6 个节点组成的 Redis 集群,其中 3 个为 主节点(A, B, C),另外 3 个为对应的从节点(A1, B1, C1)。每个主节点分别管理 5461 个槽。
初始状态:
A 节点负责槽 0-5460
B 节点负责槽 5461-10922
C 节点负责槽 10923-16383
客户端操作:
如果客户端想要获取键 “user:1000” 的值,首先计算出该键对应的槽号(例如 1234),然后根据集群配置找到负责这个槽的节点(假设是 A 节点),并向该节点发起请求。
故障处理:
假设主节点 A 发生故障,集群会检测到这一情况,并启动故障转移流程,将 A1 提升为主节点,接管原 A 节点负责的所有槽。在此过程中,其他节点也会更新自己的路由表,确保后续请求能够正确路由到新主节点。
redis中的哈希槽
在 Redis 集群中,“哈希槽”(Hash Slot)是实现数据分片和分布的关键概念。Redis 集群通过将整个键空间划分为固定的 16384 个哈希槽,实现了数据的分布式存储与负载均衡。每个键都归属于这 16384 个哈希槽中的一个,而这些哈希槽则分布在集群的不同节点上。
- 哈希槽的工作原理
分配规则:
当向 Redis 集群中添加一个键时,首先使用 CRC16 算法计算该键的哈希值。
接着,将得到的哈希值对 16384 取模(即 hash(key) % 16384),以确定该键应归属于哪个哈希槽。
最后,根据当前集群的状态,找到负责管理该哈希槽的节点,并将键存储在这个节点上。
节点分配:
在 Redis 集群启动或重新配置时,所有 16384 个哈希槽会被分配给集群中的各个主节点。
每个主节点负责处理其分配到的哈希槽中的所有操作(读写)。同时,每个主节点可以有一个或多个从节点作为备份,用于故障转移。
数据迁移与扩展性
数据迁移:当需要增加或移除集群中的节点时,可以通过重新分配哈希槽来调整数据分布。例如,如果要添加一个新的节点,可以从现有的节点中移动一部分哈希槽到新节点上。这一过程可以在不影响服务的情况下进行,从而支持在线扩展。
扩展性:由于 Redis 集群采用了固定数量的哈希槽(16384 个),因此可以根据实际需求灵活地调整集群规模。无论是增加还是减少节点,都可以通过重新分配哈希槽来实现负载均衡和数据分布优化。
客户端交互
客户端在执行命令之前,需要知道目标键位于哪个哈希槽以及该哈希槽由哪个节点管理。通常情况下,客户端会维护一份映射表,记录每个哈希槽对应的节点信息。当客户端接收到一个命令时,它会根据命令涉及的键计算出相应的哈希槽,并将请求发送到正确的节点。
如果客户端尝试访问的哈希槽不在当前连接的节点上,该节点会返回一个重定向响应(MOVED 错误),告诉客户端正确的节点地址。客户端可以根据这个信息更新自己的映射表,并将请求重发至正确的节点。
示例
假设我们有一个包含三个主节点(A, B, C)和相应从节点的 Redis 集群:
Node A:负责哈希槽 0 到 5460
Node B:负责哈希槽 5461 到 10922
Node C:负责哈希槽 10923 到 16383
如果我们想要存储一个键 “user:1000”:
计算 “user:1000” 的哈希值并对其取模 16384,假设计算结果为 3456。
根据上述分配规则,3456 属于 Node A 负责的范围(0-5460),因此这个键将会被存储在 Node A 上。
如果我们要查询这个键,客户端会重复上述步骤,找到负责哈希槽 3456 的节点(即 Node A),然后直接与该节点通信获取数据。
总结
哈希槽是 Redis 集群实现数据分片的核心机制。通过将整个键空间均匀地划分成 16384 个哈希槽,并将其分配给不同的节点,Redis 集群不仅能够有效地分散负载,还提供了良好的可扩展性和高可用性。这种设计使得 Redis 集群非常适合处理大规模数据集和高并发访问场景。
参考链接:https://zhuanlan.zhihu.com/p/663851226