《深入分布式缓存》之“从实际案例看Redis的使用”

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 版权声明:本文为半吊子子全栈工匠(wireless_com,同公众号)原创文章,未经允许不得转载。 https://blog.csdn.net/wireless_com/article/details/79159140 在一个炎热的夏天,引爆了埋藏已久的大炸弹。
版权声明:本文为半吊子子全栈工匠(wireless_com,同公众号)原创文章,未经允许不得转载。 https://blog.csdn.net/wireless_com/article/details/79159140


在一个炎热的夏天,引爆了埋藏已久的大炸弹。首先是一个产品线开发人员搭建起了一套庞大的价格存储系统,底层是关系型数据库,只用来处理一些事务性的操作和存放一些基础数据;在关系型数据库的上面还有一套MongoDB,因为MongoDB的文档型数据结构,让他们用起来很顺手,同时也可以支撑一定量的并发。在大部分的情况下,一次大数据量的计算后结果可以重用但会出现细节数据的频繁更新,所以他们又在MongoDB上搭建了一层Redis的缓存,这样就形成了数据库→MongoDB→Redis三级的方式,方案本身先不评价不是本文重,我们来看Redis这层的情况。由于数据量巨大,所以需要200GB的Redis。并且在真实的调用过程中,Redis是请求量最大的点,当然如果Redis有故障时,也会有备用方案,从后面的MongoDB和数据库中重新加载数据到Redis,就是这么一套简单的方案上线了。


当这个系统刚开始运行的时候,一切都还安好,只是运维同学有点傻眼了,200GB的Redis单服务器去做,它的故障可能性太大了,所以大家建议将它分片,没分不知道一分吓一跳,各种类型用的太多了,特别是里面还有一些当类似消息队列使用的场景。由于开发同学对Redis使用的注意点关注不够,一味的滥用,一锤了事,所以让事情变的困难了。有些侥幸不死的想法是会传染,这时的每个人都心存侥幸,懒惰心里,都想着:“这个应该没事,以后再说吧,先做个主从,挂了就起从”,这种侥幸也是对Redis的虚伪的信心,无知者无畏。可惜事情往往就是怕什么来什么,在大家快乐的放肆的使用时,系统中重要的节点MongoDB由于系统内核版本的BUG,造成整个Mongodb集群挂了!(这里不多说Mongodb的事情,这也是一个好玩的哭器)。当然对天天与故障为朋友的运维同学来说这个没什么,对整个系统来说问题也不大,因为大部分请求调用都是在最上层的Redis中完成的,只要做一定降级就行,等拉起了Mongodb集群后自然就会好了。


但此时可别忘了那个Redis,是一个200G大的Redis,更是带了个从机的Redis,所以这时的Redis是绝对不能出任何问题的,一旦有故障,所有请求会立即全部打向最低层的关系型数据库,在如此大量的压力下,数据库瞬间就会瘫痪。但是,怕什么来什么,还是出了状况:主从Redis之间的网络出现了一点小动荡,想想这么大的一个东西在主从同步,一旦网络动荡了一下下,会怎么样呢?主从同步失败,同步失败,就直接开启全同步,于是200GB的Redis瞬间开始全同步,网卡瞬间打满。为了保证Redis能够继续提供服务,运维同学,直接关掉从机,主从同步不存在了,流量也恢复正常。不过,主从的备份架构变成了单机Redis,心还是悬着的。俗话说,福无双至,祸不单行。这Redis由于下层降级的原因并发操作量每秒增加到四万多,AOF和RDB库明显扛不住。同样为了保证能持续地提供服务,运维同学也关掉了AOF和RDB的数据持久化。连最后的保护也没有了(其实这个保护本来也没用,200GB的Redis恢复太大了)。


至此,这个Redis变成了完全的单机内存型,除了祈祷它不要挂,已经没有任何方法了。悬着好久,直到修复MongoDB集群,才了事。如此的侥幸,没出大事,但心里会踏实吗?不会。在这个案例中主要的问题在于对Redis过度依赖,Redis看似为系统带来了简单又方便的性能提升和稳定性,但在使用中缺乏对不同场影的数据的分离造成了一个逻辑上的单点问题。当然这问题我们可以通过更合理的应用架构设计来解决,但是这样解决不够优雅也不够彻底,也增加了应用层的架构设计的麻烦,Redis的问题就应该在基础缓存层来解决,这样即使还有类似的情况也没有问题,因为基础缓存层已经能适应这样的用法,也会让应用层的设计更为简单(简单其实一直是架构设计所追求的,Redis的大量随意使用本身就是追求简单的副产品,那我们为不让这简单变为真实呢)


再来看第二个案例。有个部门用自己现有Redis服务器做了一套日志系统,将日志数据先存储到Redis里面,再通过其他程序读取数据并进行分析和计算,用来做数据报表。当他们做完这个项目之后,这个日志组件让他们觉得用的还很过瘾。他们都觉得这个做法不错,可以轻松地记录日志,分析起来也挺快,还用什么公司的分布式日志服务啊。于是随着时间的流逝,这个Redis上已经悄悄地挂载了数千个客户端,每秒的并发量数万,系统的单核CPU使用率也接近90%了,此时这个Redis已经开始不堪重负。终于,压死骆驼的最后一根稻草来了,有程序向这个日志组件写入了一条7MB的日志(哈哈,这个容量可以写一部小说了,这是什么日志啊),于是Redis堵死了,一旦堵死,数千个客户端就全部无法连接,所有日志记录的操作全部失败。其实日志记录失败本身应该不至于影响正常业务,但是由于这个日志服务不是公司标准的分布式日志服务,所以关注的人很少,最开始写它的开发同学也不知道会有这么大的使用量,运维同学更不知有这个非法的日志服务存在。这个服务本身也没有很好地设计容错,所以在日志记录的地方就直接抛出异常,结果全公司相当一部分的业务系统都出现了故障,监控系统中“5XX”的错误直线上升。一帮人欲哭无泪,顶着巨大的压力排查问题,但是由于受灾面实在太广,排障的压力是可以想像的。 这个案里中看似是一个日志服务没做好或者是开发流程管理不到位。而且很多日志服务也都用到了Redis做收集数据的缓冲,好像也没什么问题。其实不然,像这样大规模大流量的日志系统从收集到分析要细细考虑的技术点是巨大的,而不只是简单的写入性能的问题。在这个案例中Redis给程序带来的是超简单的性能解决方案,但这个简单是相对的,它是有场景限制的。在这里这样的简单就是毒药,无知的吃下是要害死自己的,这就像“一条在小河沟里无所不能傲慢的小鱼,那是因为它没见过大海,等到了大海……”。在这个案例的另一问题:一个非法日志服务的存在,表面上是管理问题,实质上还是技术问题,因为Redis的使用无法像关系型数据库那样有DBA的监管,它的运维者无法管理和提前知道里面放的是什么数据,开发者也无需任何申明就可以向Redis中写入数据并使用,所以这里我们发现Redis的使用没这些场景的管理后在长期的使用中比较容易失控,我们需要一个对Redis使用可治理和管控的透明层。


两个小子例子中看到在Redis乱用的那个年代里,使用他的兄弟们一定是痛的,承受了各种故障的狂轰滥炸:

q Redis被keys命令堵塞了;

q Keepalived切换虚IP失败,虚IP被释放了;

q 用Redis做计算了,Redis的CPU占用率成了100%了;

q 主从同步失败了;

q Redis客户端连接数爆了;

q ……


如何改变Redis用不好的误区

这样的乱象一定是不可能继续了,最少在同程这样的使用方式不可以再继续了,使用者也开始从喜欢到痛苦了。怎么办?这是一个很沉重的事:“一个被人用乱的系统就像一桌烧坏的菜,让你重新回炉,还让人叫好,是很困难的”。关键是已经用的这样了,总不可能让所有系统都停下来,等待新系统上线并瞬间切换好吧?这是个什么活:“高速公路上换轮胎”。

但问题出现了总是要解决的,想了再想,论了再论,总结以下几点:

(1)必须搭建完善的监控系统,在这之前要先预警,不能等到发生了,我们才发现问题;

(2)控制和引导Redis的使用,我们需要有自己研发的Redis客户端,在使用时就开始控制和引导;

(3)Redis的部分角色要改,将Redis由storage角色降低为cache角色;

(4)Redis的持久化方案要重新做,需要自己研发一个基于Redis协议的持久化方案让使用者可以把Redis当DB用;

(5)Redis的高可用要按照场景分开,根据不同的场景决定采用不同的高可用方案。

 留给开发同学的时间并不多,只有两个月的时间来完成这些事情。这事其实还是很有挑战的,考验开发同学这个轮胎到底能不换下来的时候到来了。同学们开始研发我们自己的Redis缓存系统,下面我们来看一下这个代号为凤凰的缓存系统第一版方案:

首先是监控系统。原有的开源Redis监控从大面上讲只一些监控工具,不能算作一个完整的监控系统。当然这个监控是全方位从客户端开始一直到反回数据的全链路的监控。

其次是改造Redis客户端。广泛使用的Redis客户端有的太简单有的太重,总之不是我们想要东西,比如,.Net下的BookSleeve和servicestack.Redis(同程还有一点老的.Net开发的应用),前者已经好久没人维护了,后者直接收费了。好吧,我们就开发一个客户端,然后督促全公司的研发用它来替换目前正在使用的客户端。在这个客户端里面,我们植入了日志记录,记录了代码对Redis的所有操作事件,例如耗时、key、value大小、网络断开等,我们将这些有问题的事件在后台进行收集,由一个收集程序进行分析和处理,同时取消了直接的IP端口连接方式,通过一个配置中心分配IP地址和端口。当Redis发生问题并需要切换时,直接在配置中心修改,由配置中心推送新的配置到客户端,这样就免去了Redis切换时需要业务员修改配置文件的麻烦。另外,把Redis的命令操作分拆成两部分:安全的命令和不安全的命令。对于安全的命令可以直接使用,对于不安全的命令需要分析和审批后才能打开,这也是由配置中心控制的,这样就解决了研发人员使用Redis时的规范问题,并且将Redis定位为缓存角色,除非有特殊需求,否则一律以缓存角色对待。

最后,对Redis的部署方式也进行了修改,以前是Keepalived的方式,现在换成了主从+哨兵的模式。另外,我们自己实现了Redis的分片,如果业务需要申请大容量的Redis数据库,就会把Redis拆分成多片,通过Hash算法均衡每片的大小,这样的分片对应用层也是无感知的。

当然重客户端方式不好,并且我们要做的是缓存不仅仅是单单的Redis,于是我们会做一个Redis的Proxy,提供统一的入口点,Proxy可以多份部署,客户端无论连接的是哪个Proxy,都能取得完整的集群数据,这样就基本完成了按场景选择不同的部署方式的问题。这样的一个Proxy也解决了多种开发语言的问题,例如,运维系统是使用Python开发的,也需要用到Redis,就可以直接连Proxy,然后接入到统一的Redis体系中来。做客户端也好,做Proxy也好,不只是为代理请求而是为了统一的治理Redis缓存的使用,不让乱象的出现。让缓存有一个可管可控的场景下稳定的运维,让开发者可以安全并肆无忌惮继续乱用Redis,但这个“乱”是被虚拟化的乱,因为它的底层是可以治理的。


                    图15-1 系统架构图

当然以上这些改造都需要在不影响业务的情况下进行。实现这个其实还是有不小的挑战,特别是分片,将一个Redis拆分成多个,还能让客户端正确找到所需要的key,这需要非常小心,因为稍有不慎,内存的数据就全部消失了。在这段时间里,我们开发了多种同步工具,几乎把Redis的主从协议整个实现了一遍,终于可以将Redis平滑过渡到新的模式上了。

 

欲了解更多有关分布式缓存方面的内容,请阅读《深入分布式缓存:从原理到实践》一书。

 

京东购书,扫描二维码:

 

 

 

 

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
14天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
42 5
|
18天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
34 8
|
1月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
57 16
|
1月前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
233 22
|
27天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
39 5
|
1月前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
181 7
|
1月前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
69 10
|
2月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
100 1