1.介绍
智虎Redis平台是由智虎存储平台团队基于开源Redis组件构建而成,经过不断的研发迭代,已经形成了一套完整的自动化运维服务系统,并提供了许多强大的功能。本文作者陈鹏是该系统的负责人。本文深入介绍了该系统的各个方面,值得互联网后端程序员仔细研究。
智虎技术共享:从单机到2000万的高性能缓存再分发实践QPS _ v2-98aa 01 df 20134d 401607137 eecb 11d 98 _ r-2.jpg
2.关于作者
陈鹏:他目前是智虎仓储平台集团再世平台的技术总监。2014年,他加入智湖科技平台集团,从事基础设施相关系统的开发、运营和维护,白手起家建立智湖再贴现平台,承载了智湖业务流量的快速增长。
3.技术背景
智虎,作为一个著名的中文知识内容平台,每天处理大量的访问。如何更好地承载如此庞大的访问量,提供稳定、低延迟的服务保障,是智虎科技平台学生面临的一大挑战。
智湖存储平台团队基于开源Redis组件创建的Redis平台管理系统,经过不断的研发迭代,形成了一套完整的自动化运维服务系统,提供一键式集群部署、一键式自动扩容收缩、Redis超细粒度监控、旁路流量分析等辅助功能。
目前,瑞思在智湖的应用规模如下:
1)机器总内存约为70TB,实际使用的内存约为40TB;
2)平均每秒处理约1500万个请求,峰值时每秒处理2000万个请求;
3)每天处理大约1万亿个请求;
4)单个集群每秒处理多达400万个请求;
5)总共大约有800个集群实例和独立实例;
6)在实践中运行大约16,000个Redis实例;
7)Redis使用官方版本3.0.7,一些例子使用版本4.0.11。
4.直隶的应用类型
根据业务需求,我们将Redis实例分为两种类型:独立和集群。独立实例通常用于对容量和性能要求较低的小型存储,而群集则用于对性能和容量要求较高的场景。
在群集实例类型中,当实例所需的容量超过20G或所需的吞吐量超过每秒200,000个请求时,我们将使用群集实例来承载流量。集群是一种通过中间件(客户端或中间代理等)将流量分配给多个Redis实例的解决方案。)。智湖的Redis集群方案经历了两个阶段:客户端碎片化(2015年之前使用的方案)和Twemproxy代理(2015年以来使用的方案)。
下面将介绍这两种类型的再贴现在智湖的应用实践。
5.智虎中Redis实例的应用程序类型1:独立
对于独立的实例,我们采用本机主从模式来实现高可用性,而在传统模式中只有主节点暴露在外。因为使用了本地的Redis,所以所有的Redis指令都由独立的实例支持。
对于独立实例,我们使用Redis的Sentinel集群来监控状态并对实例进行故障转移。Sentinel是Redis的一个高可用性组件。当Redis在由几个哨兵组成的哨兵集群中注册后,哨兵将检查Redis实例的健康状况。当Redis失败时,哨兵将通过流言协议检测到失败。在确认停机后,它将通过简化的Raft协议促使Slave成为新的主设备。
通常,我们只使用一个从节点进行冷备用。如果有读写分离请求,我们可以为读写分离建立多个只读从机。
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
如上图所示,通过向Sentinel集群注册主节点,实例高度可用。提交主实例的连接信息后,哨兵将主动探测所有从实例并建立连接,并定期检查健康状态。客户端通过各种资源发现策略(如简单的域名系统)来发现主节点,并计划在将来迁移到资源发现组件(如领事等)。
当主节点关闭时,哨兵集群将把从节点提升到一个新的主节点,并在其自己的pubsub信道+switch-master上广播交换消息。具体的消息格式是:
主开关
监听消息后,观察器将主动更新资源发现策略,将客户端连接指向新的主节点,并完成故障转移。有关特定的故障转移交换过程,请参见Redis监控文档-Redis。
在实际使用中注意以下几点:
1)只读从节点可以根据需要将从优先级参数设置为0,以防止在故障转移期间选择只读节点而不是热备用从节点;
2)在哨兵故障转移后,它将执行配置重写命令以登陆从属配置。如果在Redis配置中禁用CONFIG命令,则在切换期间会出现错误,并且可以修改Sentinel代码来替换CONFIG命令。
3)哨兵组不应监控太多节点,500多个切换进程偶尔会进入倾斜模式,这将导致哨兵工作异常。建议部署多个Sentinel集群,并确保每个集群监控的实例数量少于300个;
4)主节点和从节点应该跨机器部署,有能力的用户可以跨机架部署。不建议跨机房部署Redis主从实例;
5)哨兵切换功能主要取决于两个参数:毫秒后停机和故障转移timeout。毫秒后停机决定了Sentinel判断Redis节点停机的超时时间,Zhihu使用30000作为阈值。而故障转移-timeout确定两次切换之间的最短等待时间。如果切换成功率很高,故障转移-timeout可以缩短到秒,以确保切换成功。详见再贴现官方文件。
6)单机网络故障相当于机器停机。但是,如果整个机房网络发生大规模故障,主从切换会发生多次。此时,资源发现服务可能无法及时更新,这需要手动干预。
6.智湖第二类Redis实例应用:集群客户端分片方案(2015年前使用)
在智虎早期,重碎片被用于客户端碎片化。redis-shard库实现了三个哈希算法,CRC32、MD5和SHA1,并支持大多数redis命令。用户只需要使用redis-shard作为本地客户端,而不需要关注底层的碎片。
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
基于客户端的碎片化模式具有以下优势:
1)基于客户端分片的方案是集群方案中最快的,没有中间件,只有客户端需要进行一次哈希计算,没有代理,没有正式集群方案的MOVED/ASK轮;
2)不需要冗余代理机器,也不需要考虑代理部署和维护;
3)您可以定制更适合生产环境的哈希算法。
但是也存在以下问题:
1)有必要为每种语言实现客户端逻辑。在早期,Python用于整个智湖站的开发,但是后来,业务线增加了,使用的语言也增加到了Python、Golang、Lua、C/C++和JVM系统(Java、Scala、Kotlin)等。,维护成本太高;
2) MSET、MGET等可以同时操作多个键的命令不能正常使用,需要哈希标签来保证多个键在同一个片上;
3)升级比较麻烦,升级客户端需要所有的业务升级、更新和重启,在业务规模变大后无法升级;
4)容量扩展困难,需要停止存储,脚本扫描的所有键都用于迁移,缓存只能用传统的双模方法扩展;
5)由于每个客户端都必须与所有切片建立池连接,当客户端基数太大时,将在Redis建立太多的连接,而在Redis建立太多的切片将增加Python客户端的负载。
请参阅:https://github.com/zhihu/redis-shard了解具体功能
在早期,智虎的大部分业务是由Python建立的,而Redis使用的容量波动很小。redis-shard很好地响应了这一时期的业务需求,这在当时是一个相对较好的解决方案。
7.智湖重分布实例的应用类型2:集群的二层集群方案(2015年开始使用)
自2015年以来,该业务迅速增长,对再贴现的需求飙升。原有的再贴现模式已不能满足日益增长的扩张需求。我们开始研究各种聚类方案,最后选择Twemproxy作为我们的聚类方案。
Twemproxy是推特的开源软件,有以下优点:
1)性能良好且足够稳定,通过自建内存池实现缓冲区重用,代码质量非常高;
2)支持fnv1a_64、杂音、md5等哈希算法;
3)支持三种分布式算法:ketama、modula和random。
请参阅:https://github.com/twitter/twemproxy了解具体功能
但是缺点也很明显:
1)单核模式导致的性能瓶颈;
2)传统容量扩展模式仅支持关机容量扩展。
为此,我们将集群实例分为两种模式,即缓存和存储:
如果使用者可以通过丢失少量数据来确保可用性,或者如果使用者可以从存储的其余部分恢复实例中的数据,则该实例称为缓存,其余情况都是存储。
我们采用了不同的缓存和存储策略。请继续读。
7.1存储
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
对于存储,我们使用fnv1a_64算法结合modula模式,即模块化散列来分割密钥。底层的Redis使用独立模式结合Sentinel集群来实现高可用性。默认情况下,它使用一个主节点和一个从节点来提供服务。如果服务具有更高的可用性要求,可以扩展从节点。
当集群中的主节点关闭时,它将根据独立模式下的高可用性进程进行切换,Twemproxy将在连接断开后重新连接。对于处于存储模式的群集,我们不会设置自动弹出主机,也不会拒绝节点。
同时,对于存储实例,我们默认使用无记录策略,当内存使用超过指定限制时直接返回OOM错误,并且不会主动删除密钥以确保数据的完整性。
Twemproxy仅执行高性能命令转发,不执行读写分离,因此默认情况下没有读写分离功能。在实际使用中,我们不满足集群读写分离的要求。如果要进行读写分离,可以使用资源发现策略在从节点上建立Twemproxy集群,客户端可以进行读写分离路由。
7.2缓存
考虑到后端的压力(MySQL/HBase/RPC等)。),智虎的大多数服务不会降低缓存的性能。在这种情况下,缓存可用性要求高于数据一致性要求。然而,如果按照主从存储模式实现高可用性,那么一个从节点的部署策略只能容忍一个物理节点在在线环境中停机,当n个物理节点停机且高度可用时,至少需要n个从节点无疑是资源的浪费。
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
因此,我们采用Twemproxy的一致性哈希策略与自动弹出主机自动弹出策略相结合来建立Redis缓存集群。
对于缓存,我们仍然使用fnv1a_64算法进行哈希计算,但是我们使用ketama,即密钥分发的一致哈希。缓存节点没有主节点和从节点,每个切片只有一个主节点承载流量。
Twemproxy将auto_eject_hosts配置为在实例连接失败次数超过server_failure_limit次时拒绝节点,并在server_retry_timeout超时后重试。拒绝后,哈希环将与ketama一致哈希算法一起重新计算,这样即使多个物理节点宕机一次,它们也可以保持服务。
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
在实际生产环境中,应注意以下几点:
1)删除节点后,命中率会在短时间内下降,后端存储如MySQL和HBase需要进行流量监控;
2)在线环境缓存的后端碎片不要太大,建议保持在20G以内,碎片调度尽量分散,这样即使有节点停机,后端的额外压力也不会太大;
3)机器停机重启后,需要在清除数据后启动缓存实例,否则原始缓存数据和新建立的缓存数据会发生冲突,导致脏缓存。这也是一种不直接启动缓存的方法,但它会在碎片停机期间导致server_failure_limit连接定期失败。
4)server_retry_timeout和server _ failure _ lim需要仔细确定和确认。智虎使用10分钟和3次作为配置,即在3次连接失败后,节点被拒绝,并在10分钟后重新连接。
7.3Twemproxy部署
在方案的早期阶段,我们使用固定数量的物理机来部署Twemproxy,代理在物理机上启动实例。在运行期间,代理将对Twemproxy执行运行状况检查和故障恢复。由于Twemproxy仅提供完整的使用计数,代理还将执行定期差异计算,以计算Twemproxy的每秒请求数和其他指标。
后来,为了更好的故障检测和资源调度,我们引入了Kubernetes,并将Twemproxy和代理放入同一个Pod的两个容器中。底层Docker网段的配置使每个Pod能够获得独立的IP,便于管理。
首先,基于简单易用的原则,我们使用域名系统记录来发现客户端的资源,每个Twemproxy使用相同的端口号,并且在一个域名系统记录后面附加多个IP地址来对应多个Twemproxy实例。
在初始阶段,该方案易于使用,但在后期阶段,通信量日益增加,单个集群中的Twemproxy实例数量迅速超过20个。由于域名系统采用的UDP协议的数据包大小限制为512字节,单个A记录只能挂接20个左右的IP地址,如果超过这个数目,将被转换为TCP协议,如果不处理,客户端将报告错误,导致客户端启动失败。
当时,由于情况紧急,我们只能建立多个Twemproxy组,向客户端提供多个DNS地址,客户端进行轮询或随机选择。这个方案是可行的,但不够优雅。
7.4如何解决双CPU计算能力的限制
之后,我们修改了Twemproxy源代码,并添加了SO_REUSEPORT支持。
库本内特斯上的二氧化硫和二氧化硫混合物:
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
在同一个容器中,多个Twemproxy实例由启动器启动,并绑定到同一个端口,该端口由操作系统进行负载平衡。一个端口仍然暴露在外,但是内部已经被系统扩展到多个Twemproxy。
同时,启动器会定期到各Twemproxy的统计端口获取Twemproxy的运行状态进行汇总,此外,启动器还承担信号转发的责任。
原始代理不需要用于启动Twemproxy实例,因此监视器调用启动器来获取用于差异计算的聚合统计信息,并最终向外界公开实时运行状态信息。
7.5为什么没有使用官方的Redis集群方案
在2015年,我们调查了各种集群方案,在综合评估了各种方案后,我们最终选择了Twemproxy,它看起来相当古老,而不是官方的Redis集群方案和Codis。具体原因如下:
1)由1)MIGRATE:
Redis官方集群方案使用CRC16算法来计算散列值,并将密钥分散到16384个槽中。用户分配对应于每个切片的时隙。扩展容量时,用户选择插槽并遍历它们,然后执行MIGRATE命令迁移插槽中的每个键。
经过调查,发现MIGRATE命令的执行可以分为三个阶段:
A)转储阶段:源实例遍历相应键的内存空间,并序列化对应于该键的Redis对象,序列化协议与Redis RDB进程一致;
b)恢复阶段:源实例建立到对端实例的TCP连接,使用RESTORE命令将DUMP内容重构到对端,新版本的Redis将缓存对端实例的连接;
C)DEL阶段(可选):如果迁移失败,同名的密钥可能同时存在于两个节点中。此时,MIGRATE的REPLACE参数确定是否在另一端用相同的名称覆盖该键。如果是这样,另一端的密钥将被删除一次。在版本4.0之后,可以异步执行删除,而不会阻塞主进程。
经过调查,我们认为这种模式不适合志湖的生产环境。为了保证迁移的一致性,迁移的所有操作都是同步操作。当执行MIGRATE时,两端的Redis将以不同的持续时间进入BLOCK状态。
对于一个小的键,这个时间可以忽略,但是如果这个键的内存使用太大,一个MIGRATE命令将导致P95尖锐的刺,并且它将直接触发集群中的故障转移,导致不必要的切换
同时,当在迁移过程中访问处于迁移中间状态的槽的键时,根据进度可能会出现一个ASK轮。此时,客户端需要向插槽所在的另一个片发送一个询问命令来重新请求,请求延迟将加倍。
类似地,Codis在方案的初始阶段采用了相同的MIGRATE方案,但是基于类似于MIGRATE的同步命令,使用Proxy而不是第三方脚本(如Reiss-trib . Rb)来控制Reiss的迁移操作,实际上与Reiss的官方集群方案有相同的问题。
在这个巨大的关键问题上的决策权完全掌握在业务方面,有时当业务需要产生巨大的关键,如关注列表时,这是非常尴尬的。一旦业务使用不当,一个超过1MB的大密钥将导致几十毫秒的延迟,这比通常的Redis亚毫秒延迟要高得多。有时,在插槽迁移过程中,业务会意外地同时向插槽迁移的源节点和目标节点写入大量的密钥。除非您编写一个脚本来删除这些键,否则迁移将会陷入困境。
在这方面,Redis的作者在Redis 4.2的路线图中提到了非阻塞迁移,但是到目前为止,Redis 5.0将正式发布,并且没有看到相关的变化。社区中已经有相关的拉请求,这个函数可能在5.2或6.0之后被合并到主分支中,所以我们将继续等待和观察。
2)高速缓存模式下的高可用性方案不够灵活:
此外,官方集群方案只有一个主从高可用性策略,高可用性水平与从机数量正相关。如果只有一个从机,那么只能允许一台物理机停机。Redis 4.2路线图提到了仅缓存模式,它提供了一个类似Twemproxy的策略,但是它还没有实现。
3)内置哨兵导致的额外流量负载:
此外,官方的Redis集群方案在Redis中内置了哨兵功能,当节点较多(超过100个)时,在Gossip阶段会产生大量的PING/INFO/CLUSTER INFO流量。根据问题中提到的情况,有200个用版本3.2.8的节点构建的Redis集群。在没有任何客户端请求的情况下,每个节点仍然会产生40Mb/s的流量。尽管Redis在后期正式尝试压缩和修复它,但根据Redis集群机制,这部分流量在任何情况下都会在有许多节点时产生,这对于使用大型内存机器但使用千兆网卡的用户来说是一个值得注意的地方。
4)插槽存储开销:
最后,当规模较大时,每个键对应的插槽的存储开销将占用更多的内存,甚至达到4.x版之前实际内存的几倍。虽然4.x版使用rax结构进行存储,但它仍然占用大量内存。当从非官方集群模式迁移到官方集群模式时,我们应该注意这种额外的记忆。
总之,官方的Redis集群方案和Codis方案对于大多数场景来说都是很好的解决方案,但是经过仔细的研究,我们发现它们不太适合我们,因为集群数量多,使用方法多样,不同的场景会有不同的侧重点。然而,我们仍然要感谢开发这些组件的开发人员对Redis社区的贡献。
8.志湖市再贴现业务拓展实践
8.1静态膨胀
对于一个独立的实例,如果调度器观察到相应的机器仍然有空闲内存,我们只需要直接调整实例的最大内存配置和警报。同样,对于集群实例,我们通过调度程序观察每个节点所在的机器。如果所有节点所在的所有机器都有空闲内存,我们将直接更新最大内存和警报,就像扩展单个实例一样。
8.2动态扩展
但是,当机器的空闲内存不够,或者单机实例和集群的后端实例太大时,就不能直接扩展,需要动态扩展:
1)对于单个实例,如果单个实例超过30GB,并且没有像sinterstore这样的多键操作,我们将把它扩展成一个集群实例;
2)对于集群实例,我们将执行水平重切片,我们称之为重切片过程。
重塑过程:
智虎技术共享:从单机到2000万QPS并发高性能缓存的实践之路
本机Twemproxy群集方案不支持扩展。我们开发了一个数据迁移工具来扩展Twemproxy。迁移工具本质上是上游和下游之间的代理,它以一种新的方式将数据从上游传输到下游。
本地Redis主从同步使用SYNC/PSYNC命令来建立主从连接。接收到同步命令的主机将分叉出一个进程来遍历内存空间以生成一个RDB文件并将其发送给从机。发送到主机的所有写命令在执行时都将缓存在内存缓冲区中。发送RDB信号时,主机会将缓冲区中的命令和后续写命令转发给从节点。
我们开发的迁移代理将向上游发送SYNC命令,以模拟上游实例的从属实例,代理将在收到RDB后解析它。由于RDB中每个密钥的格式与RESTORE命令的格式相同,因此我们使用生成RESTORE命令根据下游密钥重新计算哈希,并使用管道将其分批发送到下游。
在等待RDB转发完成后,我们根据新的后端生成一个新的Twemproxy配置,并根据新的Twemproxy配置构建一个Canary实例,从上游的Redis后端获取密钥来测试重传过程是否正确,并在测试过程中根据大小、类型和TTL来比较密钥。
通过测试后,对于集群实例,我们使用生成的配置来替换原始的Twemproxy配置,并重新启动/重新加载Twemproxy代理。我们修改了Twemproxy代码并添加了config reload函数,但是发现在实际使用中直接重新启动实例更容易控制。对于独立实例,由于独立实例和集群实例对命令的支持不同,通常需要在与服务提供商确认后手动重新启动切换。
由于Twemproxy部署在Kubernetes,我们可以实现细粒度的灰度。如果客户端访问读写分离,我们可以先访问新集群的读流量,最后访问所有流量。
这样,与官方的Redis集群方案相比,除了在上游执行BGSAVE时由分支复制页表引起的尖峰和由重启引起的连接闪存之外,对Redis上游的影响最小。
这种扩展中存在的问题:
1)向上游发送SYNC后,上游分叉将导致尖峰:
-对于存储实例,我们使用从节点进行数据同步,这不会影响主节点接收请求;
-对于缓存实例,因为没有从属实例,所以无法避免这种峰值。如果它对峰值过于敏感,我们可以跳过RDB阶段,直接使用最新的SET消息通过PSYNC建立下游缓存。
2)在切换过程中,可以向下游写入和向上游读取:
-对于通过读写分离连接的客户端,我们将先将读取流量切换到下游实例,然后再切换写入流量。
3)一致性问题,当切换代理后端时,顺序写入相同密钥的两个命令将以两种方式写入下游:1)将上游同步写入下游2)直接写入下游。此时,可能会有应该首先执行的命令,但是1)执行滞后于2)执行,这导致命令顺序颠倒:
-这个问题在切换过程中无法避免。幸运的是,大多数应用程序都没有这个问题。如果不可接受,则只能通过停止写入和清空上游的重装代理来保证顺序;
-官方的Redis集群方案和Codis将通过阻止迁移命令来确保一致性,并且不存在这样的问题。
在实际使用中,如果上游分段安排合理,每秒可以实现数千万次迁移,而1TB实例重换只需要大约半个小时。此外,对于实际的生产环境,提前计划比在出现问题时扩大生产能力要快得多,也更安全。
9.旁路分析实践
由于生产环境的调试需要,有时有必要监控对在线Redis实例的访问。Redis提供各种监控手段,如监控命令。
但是,由于Redis单线程的限制,内置的MONITOR命令会在高负载的情况下再次运行高CPU,这对生产环境来说太危险了。其他方法(如密钥空间通知)只包含写事件,而不包含读事件,这是无法仔细观察的。
因此,我们开发了一个基于libpcap的旁路分析工具,在系统级复制流量,分析应用层流量的协议,实现旁路监控。实际测量对运行实例的影响很小。
同时,对于不带MONITOR命令的Twemproxy,旁路分析工具仍然可以分析。由于生产环境中的大多数业务都是使用Kubernetes部署在Docker中的,并且每个容器都有相应的独立的IP,因此您可以使用旁路分析工具来反向分析客户端所在的应用程序,分析业务端的使用模式,并防止异常使用。
10.未来工作
由于Redis 5.0即将发布,并且版本4.0趋于稳定,我们将逐步将实例升级到版本4.0。由此产生的特性,如内存命令、再贴现模块、新LFU算法等。,对操作和维护方面以及业务方面都有很大的帮助。
11.在末尾写
智湖架构平台团队是支持智湖整体业务的基础技术团队,开发和维护智湖几乎所有的核心基础组件,包括核心基础设施,如容器、Redis、MySQL、Kafka、LB、HBase等。该团队规模小且复杂,每个学生单独负责上述核心系统之一。
随着智虎业务规模的快速增长和业务复杂性的不断增加,我们的团队面临着越来越多的技术挑战。欢迎对技术感兴趣并渴望迎接技术挑战的小伙伴加入我们,共同构建稳定高效的智虎云平台。
----------------------------------------------------------------------------------
哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通
IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载
IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建
新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP
关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建
联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务
公有云和私有云之间有什么区别?类似融云、环信云、网易云、哇谷云?