1. 关于redis
Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存、消息代理和流媒体引擎。Redis提供数据结构,如字符串、散列、列表、集合、带范围查询的排序集合、位图、超日志、地理空间索引和流。Redis具有内置复制、Lua脚本、LRU驱逐、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster的自动分区提供高可用性。
您可以对这些类型运行原子操作,比如附加到字符串;递增散列中的值;将元素推送到列表中;计算集交集、并集和差集;或者获得排序集合中排名最高的成员。
为了获得最佳性能,Redis使用内存中的数据集。根据您的使用情况,Redis可以通过定期将数据集转储到磁盘或将每个命令附加到基于磁盘的日志中来持久化您的数据。如果您只需要一个功能丰富、联网的内存缓存,也可以禁用持久性。
Redis支持异步复制,具有快速的非阻塞同步和在网络拆分上具有部分重新同步的自动重新连接。
Redis还包括:
交易、发布/订阅、Lua脚本、寿命有限的钥匙、LRU逐出密钥、自动故障切换
您可以使用大多数编程语言中的Redis。
Redis是用ANSI C编写的,适用于大多数POSIX系统,如Linux、*BSD和Mac OS X,没有外部依赖关系。Linux和OS X是Redis开发和测试最多的两个操作系统,我们建议使用Linux进行部署。Redis可以在像SmartOS这样的Solaris派生系统中工作,但支持是最大的努力。没有官方对Windows版本的支持。
2. Keys
Redis数据类型tutorialRedis密钥是二进制安全的,这意味着你可以使用任何二进制序列作为密钥,从像“foo”这样的字符串到JPEG文件的内容。空字符串也是一个有效的键。
关于密钥的其他一些规则:
很长的钥匙不是个好主意。例如,1024字节的密钥不仅在内存方面是个坏主意,而且因为在数据集中查找密钥可能需要进行多次昂贵的密钥比较。即使手头的任务是匹配大值的存在,对其进行哈希(例如使用SHA1)也是一个更好的想法,尤其是从内存和带宽的角度来看, 所以当大量是选择hash压缩再存储。
很短的钥匙通常不是一个好主意。如果你可以写“user:1000:followers”,那么写“u1000flw”作为密钥就没有什么意义了。后者更具可读性,并且与键对象本身和值对象使用的空间相比,添加的空间较小。虽然短键显然会消耗更少的内存,但你的工作是找到正确的平衡。
试着坚持一个模式。例如,“对象类型:id”是一个好主意,就像在“user:1000”中一样。点或破折号通常用于多词字段,如“comment:4321:reply.to”或“comment:3321:reply-to”。
允许的最大密钥大小为512 MB。
关于密钥过期的一些重要注意事项:
它们可以使用秒或毫秒精度进行设置。然而,过期时间分辨率始终为1毫秒。有关过期的信息会被复制并持久化到磁盘上,当Redis服务器保持停止时,时间实际上已经过去了(这意味着Redis会保存密钥的过期日期)。
3. 数据类型
基础准备:
redisObject对象的底层结构,Redis对象,干嘛的?想象成对象头,不管你什么类型,都必须要带的,里面包含数据类型等等信息。一个RedisObject占用的字节数:4+4+24+32+64=128位/8=16字节。
结构如下:
typedef struct redisObject {
// 类型: 下面的string、list、map、set、sortset其中一个
unsigned type:4;
// 编码方式,对象底层所使用的编码
unsigned encoding:4;
// LRU时间,相对于server.lrulock
unsigned lru:LRU_BITS;
int refcount;
// 指向对象的值
void *ptr;
} robj;
-
字符串string
1、Redis字符串存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是最基本的Redis数据类型。它们通常用于缓存,但它们支持额外的功能,允许您实现计数器并执行逐位操作。
2、限制:默认情况下,单个Redis字符串的最大容量为512 MB。
3、取和设置字符串,基本命令:SET存储字符串值。 SETNX仅在密钥不存在的情况下存储字符串值。对实现锁很有用。 GET检索字符串值。 MGET在单个操作中检索多个字符串值。
4、管理计数器:
INCRBY原子递增(传递负数时递减)存储在给定密钥处的计数器。 存在另一个用于浮点计数器的命令:INCRBYFLOAT。
5、效率:大多数字符串操作都是O(1),这意味着它们非常高效。但是,要小心SUBSTR、GETRANGE和SETRANGE命令,它们可以是O(n)。在处理大型字符串时,这些随机访问字符串命令可能会导致性能问题。
6、底层实现:1、redis的string底层数据结构使用的是sds,但是sds有两种存储方式,一种是embstr,一种是raw。 2、embstr的优势在于和对象头一起分配到连续空间,只需要调用函数malloc一次就行。raw需要两次,一次是对象头,一次是sds。释放也一样,embstr释放一次,raw释放两次。 3、字符串内容可转为 long,采用 int 类型,否则长度<39(3.2版本前是39,3.2版本后分界线是44) 用embstr,其他用 raw。 4、SDS 是Redis自己构建的一种简单动态字符串的抽象类型,并将 SDS 作为 Redis 的默认字符串表示。 SDS 与 C 语言字符串结构相比,具有四大优势。
-
list类型
1、Redis列表是字符串值的链表。Redis列表经常用于:
实现堆栈和队列。
为后台工作系统构建队列管理。
2、限制:Redis列表的最大长度是2^32-1(4294967295)个元素。
3、基本命令LPUSH将一个新元素添加到列表的头部;RPUSH添加到尾部。 LPOP从列表的头部移除并返回一个元素;RPOP也做同样的事情,只是从列表的尾部开始。 LLEN返回列表的长度。 LMOVE以原子方式将元素从一个列表移动到另一个列表。 LTRIM将列表缩减为指定的元素范围。
4、阻塞命令
列表支持多个阻止命令。例如: BLPOP从列表的头部移除并返回一个元素。如果列表为空,则命令将阻塞,直到某个元素可用或达到指定的超时为止。 BLMOVE以原子方式将元素从源列表移动到目标列表。如果源列表为空,则该命令将被阻止,直到有新的元素可用为止。
5、执行效率:访问其头部或尾部的列表操作是O(1),这意味着它们是高效的。但是,操作列表中元素的命令通常是O(n)。这些例子包括LINDEX、LINSERT和LSET。运行这些命令时要小心,主要是在大列表上操作时。
6、可替代方案:当您需要存储和处理一系列不确定的事件时,可以考虑将Redis流作为列表的替代方案。
7、底层实现:list对象编码是 压缩列表(ziplist) 或者 双向链表。 1、压缩列表(主要是为了节约内存) 当创建新的列表键时,列表会优先考虑使用压缩列表,因为双向链表占用的内存比压缩列表要多,并且在有需要的时候,才从压缩列表实现转换到双向链表实现。 ziplist存储在一段连续的内存上,所以存储效率很高。但它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长时,一次realloc可能会导致大批量的数据拷贝。 没有维护双向指针:prev next,而是存储上一个 entry的长度和当前entry的长度,通过长度推算下一个元素的位置。牺牲读取的性能,获得高效的存储空间,因为简短字符串的情况下存储指针比存储entry长度更费内存,这是典型的“时间换空间”。 2、双向链表 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针(一个指向前一个节点,一个指向后一个节点,可找到前驱和后继);其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。 3、编码转换 当list对象可以同时满足以下两个条件时,则使用 压缩列表 编码: 1)新增字符串(单个元素)的长度不超过64个字节(默认值,配置参数list_max_ziplist_value可以修改); 2)list对象保存的元素数量不超过 512 个(默认值,配置参数list_max_ziplist_entries可以修改); 不能满足这两个条件的list对象需要使用 双向链表 编码。 注意:这两个默认参数可以修改,具体请看配置文件redis.conf。
-
set类型
1、Redis集合是唯一字符串(成员)的无序集合。您可以使用Redis集有效地:
跟踪唯一项目(例如,跟踪访问给定博客文章的所有唯一IP地址)。
表示关系(例如,具有给定角色的所有用户的集合)。
执行常见的集合操作,如交集、并集和差集。
2、限制:Redis列表的最大长度是2^32-1(4294967295)个元素。
3、基本命令SADD向集合添加一个新成员。 SREM从集合中删除指定的成员。 SISMEMBER测试字符串的集合成员身份。 SINTER返回两个或多个集合共有的成员集合(即交集)。 SCARD返回集合的大小(也称为基数)。
4、效率:
大多数集合操作,包括添加、删除和检查项目是否为集合成员,都是O(1)。这意味着它们的效率很高。但是,对于具有数十万或更多成员的大型集合,在运行SMEMBERS命令时应格外小心。此命令为O(n),并在单个响应中返回整个集合。另一种选择是考虑SSCAN,它允许您迭代地检索集合的所有成员。
5、可替代方案
在大型数据集(或流式数据集)上设置成员身份检查可能会占用大量内存。如果你担心内存使用,并且不需要完美的精度,可以考虑使用Bloom过滤器或Cuckoo过滤器作为一个集合的替代方案。
Redis集合经常被用作一种索引。如果您需要索引和查询数据,请考虑RediSearch和RedisJSON。
6、数据结构
Redis的set底层使用了intset和hashtable两种数据结构存储的,其中intset可以理解为一种特殊的数组,而hashtable就是普通的哈希表。
Set的底层存储intset和hashtable存在编码转换,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:1.结合对象保存的所有元素都是整数值 2.集合对象保存的元素数量不超过512个
-
Hash类型
1、Redis Hash是一种记录类型,其结构是字段值对的集合。您可以使用散列来表示基本对象和存储计数器分组等。
2、基本命令HSET设置哈希中一个或多个字段的值。 HGET返回给定字段的值。 HMGET返回一个或多个给定字段的值。 HINCRBY将给定字段的值递增所提供的整数。
3、效率: 大多数Redis hash命令都是O(1)。一些命令(如HKEYS、HVALS和HGETALL)是O(n),其中n是字段值对的数量。
4、限制: 每个散列最多可以存储4294967295(2^32-1)个字段值对。在实践中,您的哈希仅受托管Redis部署的虚拟机上的总内存的限制。
5、hash底层的结构是 ziplist 和 hashtable
当Hash的数据项较少时,Hash底层才会用压缩列表zipList进行存储数据.数据增加,底层的zipList会转成dict(Hashtable),
具体配置如下hash-max-ziplist-entries 512 hash-max-ziplist-value 64
默认情况下:
当ziplist中entry的数量超过512的时候,会转成dict
单个元素的值超过64字节的时候,会转成dict
-
sorted sets类型
1、sorted sets简介
Redis排序集是由相关分数排序的唯一字符串(成员)的集合。当多个字符串具有相同的分数时,这些字符串将按字典顺序排列。排序集的一些用例包括:
排行榜。例如,您可以使用排序集来轻松维护大型在线游戏中最高分数的有序列表。
限速器。特别是,您可以使用排序集来构建滑动窗口速率限制器,以防止过多的API请求。
2、基本命令ZADD将一个新成员和相关联的分数添加到已排序的集合中。如果该成员已经存在,则会更新分数。 ZRANGE返回在给定范围内排序的已排序集合的成员。 ZRANK返回所提供成员的等级,假设排序是按升序排列的。 ZREVRANK返回所提供成员的等级,假设排序的集合是按降序排列的。
3、效率: 大多数排序集操作都是O(log(n)),其中n是成员数。
在运行具有较大返回值(例如,数万或更多)的ZRANGE命令时要格外小心。此命令的时间复杂度为O(log(n)+m),其中m是返回的结果数。
4、可替代方案:Redis排序集有时用于索引其他Redis数据结构。如果您需要索引和查询数据,请考虑RediSearch和RedisJSON。
-
redis流
Redis流是一种数据结构,其作用类似于仅追加日志。您可以使用流实时记录和同时联合事件。Redis流用例示例包括:事件来源(例如,跟踪用户操作、点击等) 传感器监测(例如,现场设备的读数) 通知(例如,将每个用户的通知记录存储在单独的流中) Redis为每个流条目生成一个唯一的ID。您可以使用这些ID在以后检索它们的关联条目,或者读取和处理流中的所有后续条目。 Redis流支持多种微调策略(以防止流无限增长)和多种消费策略(请参阅XREAD、XREADGRUP和XRANGE)。
-
此外还有:
1、Redis geospatial: Redis地理空间索引允许您存储坐标并进行搜索。此数据结构对于查找给定半径或边界框内的附近点非常有用。
2、Redis HyperLogLog: HyperLogLog是一种估计集合基数的数据结构。作为一种概率数据结构,HyperLogLog以完美的准确性换取高效的空间利用率。Redis HyperLogLog实现最多使用12 KB,并提供0.81%的标准错误。
3、Redis bitmap是字符串数据类型的扩展,可以将字符串视为位向量。您还可以对一个或多个字符串执行逐位操作。位图用例的一些示例包括:集合的成员对应于整数0-N的情况的有效集合表示。对象权限,其中每个位代表一个特定的权限,类似于文件系统存储权限的方式。
4. redis管理
1 Memory内存
确保已启用交换,并且交换文件大小与系统上的内存大小相等。如果Linux没有设置交换,并且您的Redis实例意外消耗了太多内存,Redis可能会在内存不足时崩溃,或者Linux内核OOM杀手可能会杀死Redis进程。启用交换后,您可以检测延迟峰值并对其采取行动。
在实例中设置显式的maxmemory选项限制,以确保在接近系统内存限制时,它将报告错误而不是失败。请注意,应该通过计算Redis的开销(而不是数据)和碎片开销来设置maxmemory。因此,如果你认为你有10 GB的可用内存,请将其设置为8或9。
如果您在写操作繁重的应用程序中使用Redis,同时在磁盘上保存RDB文件或重写AOF日志,Redis最多可以使用正常使用的2倍内存。所使用的额外内存与保存过程中写入修改的内存页面数量成比例,因此通常与在此期间触摸的键(或聚合类型项目)数量成比例。确保相应地调整内存大小。
请参阅LATENCY DOCTOR和MEMORY DOCTOR命令,以帮助进行故障排除。
2 Redis security安全
2.1 Redis中的安全模型和功能
本文档从Redis的角度介绍了安全主题。它涵盖了Redis提供的访问控制、代码安全问题、通过选择恶意输入可以从外部触发的攻击,以及其他类似的主题。通过参加Redis大学的安全课程,您可以了解更多关于访问控制、数据保护和加密、安全Redis架构和安全部署技术的信息。
2.2 Security model安全模式:
Redis被设计为可由受信任环境中的受信任客户端访问。这意味着,通常将Redis实例直接暴露在互联网上,或者通常暴露在不受信任的客户端可以直接访问Redis TCP端口或UNIX套接字的环境中,这不是一个好主意。
例如,在使用Redis作为数据库、缓存或消息系统实现的web应用程序的公共上下文中,应用程序前端(web端)内的客户端将查询Redis以生成页面或执行web应用程序用户请求或触发的操作。
在这种情况下,web应用程序在Redis和不受信任的客户端(访问web应用程序的用户浏览器)之间进行访问。
通常,对Redis的不受信任的访问应该始终由一个实现ACL的层来中介,验证用户输入,并决定对Redis实例执行什么操作。
2.3 Network security网络安全
除了网络中受信任的客户端之外,所有人都应该拒绝访问Redis端口,因此运行Redis的服务器应该只能由使用Redis实现应用程序的计算机直接访问。
在单个计算机直接暴露在互联网上的常见情况下,例如虚拟化Linux实例(Linode、EC2…),Redis端口应进行防火墙保护,以防止来自外部的访问。客户端仍然可以使用环回接口访问Redis。
请注意,通过在Redis.conf文件中添加如下行,可以将Redis绑定到单个接口:
绑定127.0.0.1
由于Redis的性质,无法从外部保护Redis端口可能会对安全产生重大影响。例如,外部攻击者可以使用单个FLUSHALL命令来删除整个数据集。
2.4 Protected mode受保护模式
不幸的是,许多用户无法保护Redis实例不被外部网络访问。许多实例只是通过公共IP暴露在互联网上。从3.2.0版本开始,Redis在使用默认配置(绑定所有接口)执行时进入了一种称为保护模式的特殊模式,并且没有任何密码来访问它。在这种模式下,Redis只回复环回接口的查询,并向从其他地址连接的客户端回复一个错误,解释问题以及如何正确配置Redis。可以通过配置解决是否受保护模式。
2.5 Authentication授权
2.6 防止恶意输入的攻击
由外部客户端的恶意输入触发的攻击
有一类攻击,即使没有对实例的外部访问权限,攻击者也可以从外部触发。例如,攻击者可能会将数据插入Redis,从而触发Redis内部实现的数据结构的病态(最坏情况)算法复杂性。
攻击者可以通过web表单提供一组已知的字符串,这些字符串可以哈希到哈希表中的同一个bucket,以便将O(1)预期时间(平均时间)转换为O(N)最坏情况。这可能会消耗比预期更多的CPU,并最终导致拒绝服务。
为了防止这种特定的攻击,Redis在散列函数中使用了每次执行的伪随机种子。
Redis使用qsort算法实现SORT命令。目前,该算法不是随机的,因此可以通过仔细选择正确的输入集来触发二次最坏情况行为。
字符串转义和NoSQL注入
Redis协议没有字符串转义的概念,因此在正常情况下使用普通客户端库是不可能进行注入的。该协议使用带前缀的长度字符串,并且是完全二进制安全的。
由于EVAL和EVALSHA命令执行的Lua脚本遵循相同的规则,因此这些命令也是安全的。
虽然这将是一个奇怪的用例,但应用程序应该避免使用从不受信任的源获得的字符串来编写Lua脚本的主体
3 High availability with Redis Sentinel
3.1 Redis Sentinel在不使用Redis Cluster时为Redis提供高可用性。
Redis Sentinel还提供其他辅助任务,如监控、通知,并充当客户端的配置提供商。
这是Sentinel在宏观层面(即大局)的完整功能列表:
监测。Sentinel不断检查您的主实例和副本实例是否按预期工作。
通知。Sentinel可以通过API通知系统管理员或其他计算机程序,其中一个受监控的Redis实例出现问题。
自动故障切换。如果主机未按预期工作,Sentinel可以启动故障切换过程,将复制副本升级为主机,将其他附加复制副本重新配置为使用新主机,并通知使用Redis服务器的应用程序连接时要使用的新地址。
配置提供程序。Sentinel充当客户端服务发现的权威来源:客户端连接到Sentinel,以询问负责给定服务的当前Redis主机的地址。如果发生故障转移,Sentinel将报告新地址。
3.2 Redis Sentinel是一个分布式系统:
Sentinel本身被设计为在多个Sentinel进程协同工作的配置中运行。多个Sentinel进程协作的优势如下:
当多个Sentinel一致认为给定的主机不再可用时,就会执行故障检测。这降低了误报的概率。
即使不是所有的Sentinel流程都在工作,Sentinel也能工作,使系统能够抵御故障。毕竟,故障切换系统本身就是一个单一的故障点,这并没有什么乐趣。
Sentinel、Redis实例(主实例和副本)以及连接到Sentinel和Redis的客户端的总和也是一个具有特定属性的更大的分布式系统。
3.3 部署Sentinel之前需要了解的基本信息
一个健壮的部署至少需要三个Sentinel实例。
三个Sentinel实例应该放置在被认为以独立方式失败的计算机或虚拟机中。例如,在不同的可用性区域上执行的不同物理服务器或虚拟机。
Sentinel+Redis分布式系统不能保证在故障期间保留已确认的写入,因为Redis使用异步复制。然而,有一些方法可以部署Sentinel,使丢失写入的窗口仅限于某些时刻,而还有其他不太安全的方法可以部署它。
您的客户需要Sentinel的支持。流行的客户端库支持Sentinel,但不是全部。
如果您不在开发环境中不时进行测试,那么没有HA设置是安全的,如果可以的话,在生产环境中进行测试会更好,如果它们有效的话。你可能有一个错误的配置,只有当为时已晚时(凌晨3点,你的主人停止工作时)才会变得明显。
Sentinel、Docker或其他形式的网络地址转换或端口映射应小心混合:Docker执行端口重新映射,打破Sentinel对其他Sentinel进程的自动发现和主进程的副本列表。有关更多信息,请查看本文档后面关于Sentinel和Docker的部分。
4 主从复制
问题太多,不建议用
5 Horizontal scaling with Redis Cluster redis集群水平扩展
5.1 Redis Cluster能力:
1、在多个节点之间自动拆分数据集。
2、当节点的一个子集出现故障或无法与集群的其他部分通信时,继续操作。
5.2 port端口
每个Redis Cluster节点都需要两个打开的TCP连接:一个Redis TCP端口用于为客户端提供服务,例如6379,另一个端口称为集群总线端口。默认情况下,集群总线端口通过在数据端口上添加10000(例如16379)来设置
5.3 集群分片
Redis Cluster不使用一致的哈希,而是一种不同形式的分片,其中每个密钥在概念上都是我们所说的哈希槽的一部分。
Redis集群中有16384个哈希槽,为了计算给定密钥的哈希槽,我们只需取密钥的CRC16模16384。
Redis集群中的每个节点都负责哈希槽的一个子集,因此,例如,您可能有一个具有3个节点的集群,其中:
节点A包含从0到5500的哈希槽。
节点B包含从5501到11000的散列槽。
节点C包含从11001到16383的散列槽。
这使得添加和删除集群节点变得很容易。例如,如果我想添加一个新的节点D,我需要将一些哈希槽从节点a、B、C移动到D。类似地,如果我想要从集群中删除节点a,我可以将a提供的哈希槽移动到B和C。一旦节点a为空,我就可以将其从集群中完全删除。
将哈希槽从一个节点移动到另一个节点不需要停止任何操作;因此,添加和删除节点,或者更改节点所占哈希槽的百分比,都不需要停机。
Redis Cluster支持多个密钥操作,只要单个命令执行(或整个事务,或Lua脚本执行)中涉及的所有密钥都属于同一个哈希槽。用户可以通过使用一种名为哈希标签的功能,强制多个密钥成为同一哈希槽的一部分。
Redis Cluster规范中记录了哈希标记,但要点是,如果键中的{}个方括号之间有一个子字符串,那么只有字符串中的内容才会被哈希。例如,密钥user:{123}:profile和user:{123}:account保证位于同一哈希槽中,因为它们共享相同的哈希标签。因此,您可以在同一个多键操作中对这两个键进行操作。
副本的概念:
当创建集群时(或在以后的时间),我们向每个主节点添加一个副本节点,这样最终的集群由作为主节点的a、B、C和作为副本节点的A1、B1、C1组成。这样,如果节点B出现故障,系统可以继续运行。
节点B1复制了B,而B失败了,集群将提升节点B1为新的主节点,并将继续正常运行。
但是,请注意,如果节点B和B1同时发生故障,Redis Cluster将无法继续运行。
5.4 Redis群集一致性保证
Redis群集无法保证强一致性。实际上,这意味着在某些情况下,Redis Cluster可能会丢失系统向客户端确认的写入。
Redis Cluster可能丢失写入的第一个原因是它使用异步复制。这意味着在写入过程中会发生以下情况:
你的客户写信给B大师。
主B对您的客户回复“OK”。
主控器B将写入传播到其副本B1、B2和B3。
正如你所看到的,B在回复客户端之前不会等待B1、B2、B3的确认,因为这对Redis来说是一个令人望而却步的延迟惩罚,所以如果你的客户端写了一些东西,B确认了写操作,但在能够将写操作发送到其副本之前崩溃,其中一个副本(没有收到写操作)可以升级为主副本,永远失去写操作。
这与大多数配置为每秒将数据刷新到磁盘的数据库的情况非常相似,因此,由于过去使用不涉及分布式系统的传统数据库系统的经验,您已经能够对这种情况进行推理。同样,您可以通过强制数据库在回复客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致性能低下。这相当于Redis集群中的同步复制。
基本上,在性能和一致性之间需要进行权衡。
Redis Cluster在绝对需要时支持同步写入,通过WAIT命令实现。这使得失去写作的可能性大大降低。然而,请注意,即使使用同步复制,Redis Cluster也无法实现强一致性:在更复杂的故障场景下,无法接收写入的副本总是有可能被选为主副本。
还有一种值得注意的情况是,Redis Cluster将丢失写入,这种情况发生在网络分区期间,其中客户端与少数实例隔离,其中至少包括一个主机。
以我们的6个节点集群为例,该集群由A、B、C、A1、B1、C1组成,有3个主节点和3个副本。还有一个客户,我们将称之为Z1。
在分区发生后,有可能在分区的一侧有a、C、A1、B1、C1,而在另一侧有B和Z1。
Z1仍然能够向B写入,B将接受其写入。如果分区在很短的时间内恢复,集群将正常继续。然而,如果该分区持续足够的时间,使B1在该分区的多数侧被提升为主分区,那么Z1在此期间发送给B的写入将丢失。
Z1可以向B发送的写入量有一个最大窗口:如果分区的多数侧已经过了足够的时间来选择副本作为主节点,则少数侧的每个主节点都将停止接受写入。
这个时间量是Redis Cluster非常重要的配置指令,称为节点超时。
节点超时后,主节点被视为发生故障,可以由其一个副本替换。类似地,在没有主节点能够感测大多数其他主节点的情况下经过节点超时之后,它进入错误状态并停止接受写入。
5.5 集群命令
查看节点信息
redis-cli -p 7000 cluster nodes | grep myself
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460
集群健康度
redis-cli --cluster check 127.0.0.1:7000
5 Redis persistence
持久性是指将数据写入持久存储器,如固态硬盘(SSD)。Redis提供了一系列持久性选项。其中包括:
5.1 持久化
5.1.1 RDB(Redis数据库)
RDB持久性以指定的时间间隔执行数据集的时间点快照。
快照的释义:
默认情况下,Redis将数据集的快照保存在磁盘上一个名为dump.rdb的二进制文件中。如果数据集中至少有M个更改,您可以将Redis配置为每N秒保存一次数据集,也可以手动调用save或BGSAVE命令。
例如,如果至少更改了1000个密钥,此配置将使Redis每60秒自动将数据集转储到磁盘:
save 60 1000
这种策略被称为快照。
它是如何工作的
1、每当Redis需要将数据集转储到磁盘时,就会发生以下情况:
2、Redis分叉。我们现在有一个子进程和一个父进程。
3、孩子开始将数据集写入一个临时的RDB文件。
4、当孩子完成新的RDB文件的编写后,它会替换旧的RDB。
这种方法允许Redis从写时复制语义中受益。
5.1.2 AOF(仅附加文件)
AOF持久性记录服务器接收到的每个写入操作。然后,可以在服务器启动时再次重播这些操作,从而重建原始数据集。使用与Redis协议本身相同的格式记录命令。
5.1.2.1 快照不是完全持久化的。
如果你运行Redis的计算机停止运行,电源线故障,或者你不小心杀死了-9你的实例,那么写入Redis的最新数据将丢失。虽然这对某些应用程序来说可能不是什么大不了的事情,但也有完全持久性的用例,在这些情况下,单独使用Redis快照是不可行的。
仅追加文件是Redis的一种替代、完全持久的策略。它在1.1版中提供。
您可以在配置文件中打开AOF:
appendonly yes
从现在起,每当Redis收到更改数据集的命令(例如SET)时,它都会将其附加到AOF中。当您重新启动Redis时,它将重新播放AOF以重建状态。
自Redis 7.0.0以来,Redis使用了多部分AOF机制。也就是说,原始的单个AOF文件被拆分为基本文件(最多一个)和增量文件(可能有多个)。基本文件表示重写AOF时存在的数据的初始快照(RDB或AOF格式)。增量文件包含自上一个基本AOF文件创建以来的增量更改。所有这些文件都放在一个单独的目录中,并由清单文件跟踪。
######5.1.2.2 日志重写logs rewrite
随着写操作的执行,AOF变得越来越大。例如,如果您将计数器递增100次,那么数据集中的单个键将包含最终值,但AOF中有100个条目。重建当前状态不需要其中的99个条目。
重写是完全安全的。当Redis继续追加到旧文件时,会生成一个全新的文件,只需创建当前数据集所需的最小操作集,一旦第二个文件准备好,Redis就会切换这两个文件并开始追加到新文件。
因此Redis支持一个有趣的功能:它能够在后台重建AOF,而不会中断对客户端的服务。每当您发出BGREWRITEAOF时,Redis都会编写在内存中重建当前数据集所需的最短命令序列。如果您在Redis 2.2中使用AOF,则需要不时运行BGREWRITEAOF。由于Redis 2.4能够自动触发日志重写(更多信息请参阅示例配置文件)。
自Redis 7.0.0以来,当安排AOF重写时,Redis父进程会打开一个新的增量AOF文件以继续写入。子进程执行重写逻辑并生成新的基本AOF。Redis将使用一个临时清单文件来跟踪新生成的基本文件和增量文件。当它们准备好后,Redis将执行原子替换操作,使这个临时清单文件生效。为了避免在AOF重写重复失败和重试的情况下创建许多增量文件的问题,Redis引入了AOF重写限制机制,以确保失败的AOF重写以越来越慢的速度重试。
5.1.3 无持久性
您可以完全禁用持久性。这有时在缓存时使用。
5.1.4 RDB+AOF
您也可以将AOF和RDB组合在同一个实例中。
5.1.5 两种方式优劣对比
方式 | RDB | AOF |
---|---|---|
优点 | DB是Redis数据的一种非常紧凑的单文件时间点表示。RDB文件非常适合备份。例如,您可能希望在最近的24小时内每小时归档一次RDB文件,并在30天内每天保存一个RDB快照。这使您能够在发生灾难时轻松恢复不同版本的数据集。RDB非常适合灾难恢复,它是一个紧凑的文件,可以传输到远程数据中心,也可以传输到AmazonS3(可能是加密的)。RDB最大限度地提高了Redis的性能,因为Redis父进程要想持久化,唯一需要做的工作就是派生一个子进程来完成其余的工作。父进程永远不会执行磁盘I/O或类似操作。与AOF相比,RDB允许使用大型数据集更快地重新启动。在副本上,RDB支持在重新启动和故障切换后进行部分重新同步。 | 如果您需要在Redis停止工作的情况下(例如停电后)将数据丢失的可能性降至最低,那么RDB就不好了。您可以在生成RDB的地方配置不同的存储点(例如,在对数据集进行至少五分钟和100次写入之后,您可以拥有多个存储点)。然而,您通常会每五分钟或更长时间创建一个RDB快照,因此,如果Redis因任何原因在没有正确关闭的情况下停止工作,您应该做好丢失最近几分钟数据的准备。RDB需要经常分叉(),以便使用子进程在磁盘上持久存在。如果数据集很大,fork()可能会很耗时,并且可能导致Redis在几毫秒内停止为客户端提供服务,如果数据集非常大并且CPU性能不好,则可能会停止一秒钟。AOF也需要fork(),但频率较低,并且您可以调整想要重写日志的频率,而不会在持久性方面做出任何权衡。 |
缺点 | 使用AOF Redis要持久得多:您可以有不同的fsync策略:完全不进行fsync、每秒进行fsync和每次查询都进行fsync。使用每秒fsync的默认策略,写入性能仍然很好。fsync是使用后台线程执行的,当没有进行fsync时,主线程会努力执行写入操作,因此您只能损失一秒钟的写入操作。AOF日志是一个仅追加的日志,因此在停电时不会出现查找或损坏问题。即使由于某种原因(磁盘已满或其他原因),日志以半写命令结束,redis check aof工具也能很容易地修复它。Redis能够在AOF过大时自动在后台重写它。重写是完全安全的,因为当Redis继续追加到旧文件时,会用创建当前数据集所需的最小操作集生成一个全新的文件,一旦第二个文件准备好,Redis就会切换这两个文件并开始追加到新文件。AOF以易于理解和解析的格式包含一个接一个的所有操作的日志。您甚至可以轻松导出AOF文件。例如,即使您意外地使用FLUSHALL命令刷新了所有内容,只要在此期间没有执行日志重写,您仍然可以通过停止服务器、删除最新命令并重新启动Redis来保存数据集。 | 对于同一数据集,AOF文件通常比等效的RDB文件大。根据具体的fsync策略,AOF可能比RDB慢。一般来说,将fsync设置为每秒一次,性能仍然很高,禁用fsync后,即使在高负载下,它也应该与RDB一样快。尽管如此,即使在巨大的写负载的情况下,RDB也能够提供更多关于最大延迟的保证 |
5.2 推荐持久化方式
如果你想要与PostgreSQL所能提供的数据安全程度相当,那么你应该使用这两种持久性方法。
如果您非常关心自己的数据,但在发生灾难时仍能承受几分钟的数据丢失,那么只需单独使用RDB即可。
有很多用户单独使用AOF,但我们不鼓励这样做,因为不时地使用RDB快照对于进行数据库备份、更快地重新启动以及在AOF引擎出现错误的情况下都是一个好主意。
5.3 仅追加文件的持久性如何?
您可以配置Redis在磁盘上fsync数据的次数。有三种选择:
appendfsync-always:fsync每次将新命令附加到AOF时。非常非常慢,非常安全。请注意,在执行来自多个客户端或管道的一批命令后,这些命令会附加到AOF中,因此这意味着一次写入和一次fsync(在发送回复之前)。
appendfsync everysec:fsync everysec。足够快(因为2.4版本可能和快照一样快),如果发生灾难,您可能会丢失1秒的数据。
appendfsync否:永远不要进行fsync,只需将数据交到操作系统手中即可。更快、更不安全的方法。在这种配置下,Linux通常会每30秒刷新一次数据,但这取决于内核的精确调整。
建议的(也是默认的)策略是每秒进行一次fsync。它既快速又相对安全。在实践中,总是策略非常慢,但它支持组提交,因此如果有多个并行写入,Redis将尝试执行单个fsync操作。
5.4 如果我的AOF被截断,我该怎么办?
可能是服务器在写入AOF文件时崩溃,或者存储AOF文件的卷在写入时已满。当这种情况发生时,AOF仍然包含表示数据集的给定时间点版本的一致数据(使用默认的AOF fsync策略,该数据集可能会旧到1秒),但AOF中的最后一个命令可能会被截断。Redis的最新主要版本无论如何都可以加载AOF,只需丢弃文件中最后一个格式不正确的命令。在这种情况下,服务器将发出如下日志:
* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled
#由于启用了AOF加载截断,因此仍加载了AOF
如果您愿意,可以更改默认配置以强制Redis在这种情况下停止,但默认配置是继续,无论文件中的最后一个命令的格式是否正确,以确保重新启动后的可用性。
较旧版本的Redis可能无法恢复,可能需要执行以下步骤:
1、制作AOF文件的备份副本。
2、使用redis附带的redis check aof工具修复原始文件:
3、$redis检查aof--修复<filename>
4、可以选择使用diff-u来检查两个文件之间的区别。
5、使用固定文件重新启动服务器。
5.5 如果我的AOF被破坏了,我该怎么办?
如果AOF文件不仅被截断,而且被中间的无效字节序列破坏,情况会更加复杂。Redis将在启动时抱怨并将中止:
* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
最好的方法是运行redis check aof实用程序,最初不使用–fix选项,然后了解问题,跳到文件中给定的偏移量,看看是否可以手动修复文件:aof使用与redis协议相同的格式,手动修复非常简单。否则,可以让实用程序为我们修复文件,但在这种情况下,从无效部分到文件结尾的所有AOF部分都可能被丢弃,如果损坏发生在文件的初始部分,则会导致大量数据丢失。
5.6 AOF如何工作?
日志重写使用了已用于快照的写时复制技巧
版本 | Redis >= 7.0 | Redis < 7.0 |
---|---|---|
方式 | Redis fork,所以现在我们有了一个子进程和一个父进程。子进程开始在临时文件中写入新的基本AOF。父级打开一个新的增量AOF文件以继写入更新。如果重写失败,旧的基本文件和增量文件(如果有的话)加上这个新打开的增量文件代表了完整的更新数据集,所以我们是安全的。当子进程成重写基本文件时,父级会得到一个信号,并使用新打开的增量文件和子级生成的基本文件来构建临时清单,并将其持久化。利润现在Redis对清单文件进行原子交换,以便AOF重写的结果生效。Redis还会清理旧的基本文件和任何未使用的增量文件。 | Redis分叉,所以现在我们有了一个子进程和一个父进程。子进程开始在一个临时文件中写入新的AOF。父级将所有新更改累积在内存缓冲区中(但同时将新更改写入旧的仅追加文件中,因此如果重写失败,我们是安全的)。当子进程完成对文件的重写时,父级会得到一个信号,并将内存中的缓冲区附加到子进程生成的文件的末尾。现在Redis将新文件自动重命名为旧文件,并开始将新数据附加到新文件中。 |
6 过期key如何处理
有三类惰性清除、定时清理、内存不够时清理
6.1 惰性清除
当你访问到过期的key时,会将这个过期的key执行删除命令: del key;
6.2 定时清理
Redis还通过定时清理的方式来清理过期键。Redis会定期执行一个任务,通过随机选择一定数量的键,然后检查这些键是否过期,如果过期则删除这些键。
定时清理的配置:并没有直接提供配置redis.conf中,是通过lua脚本+定时策略实现
#!/bin/bash
# 获取当前日期
date=$(date +%Y-%m-%d)
# 获取Redis上次清理日期
pre_date=$(redis-cli get redis_clean_date)
# 若日期不相同,则清理缓存,并更新日期
if [ '$date' != '$pre_date' ]; then
redis-cli flushall
redis-cli set redis_clean_date $date
fi
Redis会通过设置一个定时器来周期性地执行清理任务,定时器的时间间隔可以通过配置文件中的hz参数来指定,默认情况下为10。清理任务会随机选择一批键,然后检查这些键是否过期,如果过期则删除这些键。
在Redis 5.0之前,Redis使用的是一个简单的定时器事件,每隔一定时间就执行一次清理任务。但是由于这种方式的效率不高,所以在Redis 5.0之后,引入了一个新的机制——dirty list。dirty list是一个双端队列,保存了需要被清理的过期键的列表。当一个过期键被发现时,会被加入到dirty list中;当Redis执行清理任务时,会从dirty list中取出一定数量的过期键进行清理。这种方式的效率更高,可以更快地清理过期键。
6.3 内存不够时清理
当执行写入命令时,如果发现内存不够,那么就会按照配置的淘汰策略清理内存,淘汰策略一般有6种。 常见的算法就是lfu, lru等