但有时候,纵然利用了 Cache,却创造系统依然卡顿宕机,是由于 Cache 技能不好吗?非也,实在这是缓存的管理事情没有做好。
2018 年 5 月 18-19 日,由 51CTO 主理的环球软件与运维技能峰会在北京召开。
在 19 日下午“高并发与实时处理”分会场,同程艺龙机票奇迹群 CTO 王晓波带来了《高并发场景的缓存管理》的主题演讲。

他针对如何让缓存更适宜高并发利用、如何精确利用缓存、如何通过管理化解缓请安题等热点展开了阐述。
对付我们来说,我们是 OTA 的角色,以是有大量的数据要打算处理变为可售卖商品,总的来说是“商品搬运工,而非生产商”。
以是面对各种大数据并发运行的运用处景,我们须要通过各种缓存技能来提升做事的质量。
想必大家都听说过做事的管理和数据的管理,那么是否听说过缓存的管理呢?诚然,在许多场景下,Cache 成了应对各处涌现高并发问题的一颗“银弹”。
但是它并非是放之四海皆准的,有时它反而成了一颗导致系统“挂掉”的自尽子弹。有时候这种缘故原由的涌现并非 Cache 本身的技能不好,而是我们没有做好管理。
下面,我们将从三个方面来详细谈论缓存的管理:
缓存利用中的一些痛点如何用好缓存,用精确缓存如何通过管理让缓存的问题化为无形缓存利用中的一些痛点
我们同程的业务特点是:OTA 类商品,没有任何一个价格是固定的。像酒店,客户本日订、来日诰日订、连续订三天、订两天,是否跨周末,他们末了得出的价格都是不一样的。
价格随着韶光的变革而颠簸的。这些颠簸会引发大量的打算,进而带来性能上的损耗。
要办理性能的损耗问题,我们势必要插入各种 Cache,包括:价格的 Cache、韶光段的 Cache、库存的 Cache。而且这些 Cache 的写入数据量远大于全体外部的要求数据量,即:写多于读。
下面先容同程缓存利用的历史:
一开始,我们仅利用一台 Memcache 来供应缓存做事。后来,我们创造 Memcache 存在着支持并发性不好、可运维性欠佳、原子性操作不足、在误操作时产生数据不一致等问题。因此,我们转为利用 Redis,以单线程担保原子性操作,而且它的数据类型也比较多。当有一批新的业务逻辑被写到 Redis 中时,我们就把它当作一个累加计数器。当然,更有甚者把它当作数据库。由于数据库比较慢,他们就让数据线先写到 Redis,再落盘到数据库中。
随后,我们创造在单机 Redis 的情形下,Cache 成了系统的“命门”。哪怕上层的打算尚属良好、哪怕流量并不大,我们的做事也会“挂掉”。于是我们引入了集群 Redis。同时,我们用 Java 措辞自研了 Redis 的客户端。我们也在客户端里实现了二级 Cache。不过,我们创造还是会偶尔涌现错乱的问题。后来,我们还考试测验了分布式 Cache,以及将 Redis 支配到 Docker 里面。终极,我们创造这些问题都是跟场景干系。如果你所构建的场景较为紊乱,则直接会导致底层无法供应做事。
下面我们来看看有哪些须要管理的场景,普通地说便是有哪些“坑”须要“填”。
早期在单机支配 Redis 做事的时期,我们针对业务系统支配了一套利用脚本运维的平台。
当时在一台虚机上能跑六万旁边的并发数据,这些对付 Redis 做事器来说基本够用了。
但是昔时夜量支配,并达到了数百多台时,我们碰到了两个问题:
面对高并发的性能需求,我们无法单靠脚本进走运维。一旦运维操作涌现失落误或失落控,就可能导致 Redis 的主从切换失落败,乃至引起做事宕机,从而直接对全体业务端产生影响。运用调用的缭乱。在采取微做事化之前,我们面对的每每是一个拥有各种模块的大系统。而在利用场景中,我们常把 Redis 算作数据库、存有各种工程的数据源。同时我们将 Cache 视为一个黑盒子,将各种运用数据都放入个中。
例如,对付一个订单交易系统,你可能会把订单积分、订单解释、订单数量等信息放入个中,这样就导致了大量的业务模块被耦合于此,同时所有的业务逻辑数据块也集中在了 Redis 处。
那么就算我们去拆分微做事、做代码解耦,可是多数情形下缓存池中的大数据并没有得到解耦,多个做事端仍旧通过 Redis 去共享和调用数据。
一旦涌现宕机,就算你能对做事进行降级,也无法对数据本身采纳降级,从而还是会导致整体业务的“挂掉”。
薄弱的数据消逝了。由于大家都习气把 Redit 当作数据库利用(虽然大家都知道在工程中不应该如此),毕竟它不是数据库,没有持久性,以是一旦数据丢失就会涌现大的麻烦。
为了防止单台挂掉,我们可以采取多台 Redis。此时运维和运用分别有两种方案:
运维认为:可以做“主从”,并供应一个浮动的虚拟 IP(VIP)地址。在一个节点涌现问题时,VIP 地址不用变更,直接连到下一个节点便可。运用认为:可以在运用客户端里写入两个地址,并采纳“哨兵”监控,来实现自动切换。这两个方案看似没有问题,但是架不住 Redis 的滥用。我们曾经碰到过一个现实的案例:如上图右下角所示,两个 Redis 根据主从关系可以相互切换。
按照需求,存有 20G 数据的主 Redis 开始对从 Redis 进行同步。此时网络涌现卡顿,而运用恰好创造自己的要求也相应变慢了,因此上层运用根据网络故障采纳主从切换。
然而此时由于主从 Redis 正好处于同步状态,资源花费殆尽,那么在上次运用看来此时主从 Redis 都是不可达的。
我们经由深入排查,终极创造是在 Cache 中某个表的一个 Key 中,被存放了 20G 的数据。
而在程序层面上,他们并没有掌握好该 Key 的消逝韶光(如一周),因而造成了该 Key 被持续追加增大的状况。
由上可见,就算我们对 Redis 进行了拆分,这个巨大的 Key 仍会存在于某一个“片”上。
如上图所示,仍以 Redis 为例,我们能够监控的方面包括:
当前客户真个连接数客户真个输出与输入情形是否涌现堵塞被分配的全体内存总量主从复制时的状态信息集群的情形各做事器每秒实行的命令数量可以说,这些监控的方面并不能及时地创造上述 20 个 G 的 Key 数据。再比如:常日系统是在客户下订单之后,才增加会员积分。
但是在运用设计上却将核心订单里的核心 Key,与本该滞后增加的积分赞助进程,放在了同一个实例之中。
由于我们能够监控到的都是些延迟信息,因此这种将级别高的数据与级别低的数据稠浊的情形,是无法被监控到的。
上面是一段运维与开拓的真实对话,曾发生在我们公司内部的 IM 上,它反响了在 DevOps 推进之前,运维与开拓之间的抵牾。
开拓问:Redis 为什么不能访问?
运维答:刚才做事器因内存故障自动重启了。其背后的缘故原由是:一个 Cache 的故障导致了某个业务的故障。业务认为自己的代码没有问题,缘故原由在于运维的 Cache 上。
开拓问:为什么我的 Cache 的延迟这么大?
运维答:创造开拓在此处放了几万条数据,从而影响了插入排序。
开拓问:我写进去的 Key 找不到?肯定是 Cache 出错了。这实在是运维 Cache 与利用 Cache 之间的最大抵牾。
运维答:你的 Redis 超过最大限定了,根本就没写成功,或者写进去就直接被淘汰了。这便是大家都把它当成黑盒所带来的问题。
开拓问:刚刚为何读取全部失落败?
运维答:网络临时中断,在全同步完成之前,从机的读取全部失落败了。这是一个非常经典的问题,当时运维为了简化起见,将主从代替了集群模式。
开拓问:我的系统须要 800G 的 Redis,何时能准备好?
运维答:我们线上的做事器最大只有 256 G。
开拓问:为什么 Redis 慢得像驴一样,是否做事器出了故障?
运维答:对千万级的 Key,利用 Keys,肯定会慢。
由上可见这些问题既有来自运维的,也有来自开拓的,同时还有当前技能所限定的。
我们在应对并发查询时,只看重它给我们带来的“快”这一性能特点,却忽略了对 Cache 的利用规范,以及在设计时须要考虑到的各种本身缺陷。
如何用好缓存,用精确缓存?
因此在某次重大故障发生之后,我们总结出:没想到初始状态下只有 30000 行代码的小小 Redis 竟然能带来如此神奇的功能。
以至于它在程序员手中变成了一把“见到钉子就想锤的锤子”,即:他们瞥见任何的需求都想用缓存去办理。
于是他们相继开拓出来了基于缓存的日志搜集器、倒计时、计数器、订单系统等,却忘却了它本身只是一个 Cache。一旦涌现了故障,它们将如何去担保其本身呢?
下面我们来看看缓存故障的详细成分有哪些?
过度依赖
即:明明不须要设置缓存之处,却非要用缓存。程序员们常认为某处可能会在将来涌现大的并发量,故放置了缓存,却忘却了对数据进行隔离,以及利用的办法是否精确。
例如:在某些代码中,一个函数会实行一到两百次 Cache 的读取,通过反复的 get 操作,对同一个 Key 进行连续的读取。
试想,一次并发会带给 Redis 多少次操作呢?这些对付 Redis 来说负载是相称巨大的。
数据落盘
这是一个高频次涌现的问题。由于大家确实须要一个高速的 KV 存储,来实现数据落盘需求。
因此他们都会把全体 Cache 当作数据库去利用,将任何不许可丢失的数据都放在 Cache 之中。
纵然公司有各种利用规范,此征象仍是无法杜绝。终极我们在 Cache 平台上真正做了一个 KV 数据库供程序员们利用,并且哀求他们在利用的时候,必须声明用的是 KV 数据库还是 Cache。
超大容量
由于大家都知道“放到内存里是最快的”,因此他们对付内存的需求是无穷尽的。更有甚者,有人曾向我提出 10 个 T 容量的需求,而根本不去考虑营收上的本钱。
雪崩效应
由于我们利用的是大量依赖于缓存的数据,来为并发供应支撑,一旦缓存涌现问题,就会产生雪崩效应。
即:表面的流量还在,你却不得不重启全体缓存做事器,进而会造成 Cache 被清空的情形。
由于断绝了数据的来源,这将导致后真个做事连片“挂掉”。为了防止雪崩的涌现,我们会多写一份数据到特定磁盘上。
其数据“新鲜度”可能不足,但是当雪崩发生时,它会被加载到内存中,以防止雪崩的下一波冲击,从而能够顺利地过渡到我们重新将“新鲜”的数据灌进来为止。
我们对上面提到的“坑”总结一下:
最厉害的是:利用者乱用、滥用和那么到底缓存应该如何被管理呢?从真正的开拓哲学角度上说,我们想要的是一个百变的魔术箱,它能够快速地自我变革与处理,而不须要开拓和运维职员担心滥用的问题。
其余,其他须要应对的方面还包括:运用对缓存大小的需求就像饕餮蛇一样平常,一堆孤岛般的单机做事器,缓存做事运维则像一个迷宫。
因此,我们希望构建的是一种能适用各种运用处景的缓存做事,而不是冷冰冰的 Cache Server。
起初我们考试测验了各种现成的开源方案,但是后来创造它们或多或少存在着一些问题。
例如:
Cachecloud,对付支配和运维方面欠佳。Codis,本身做了一个很大的集群,但是我们考虑到当这么一个超大池涌现问题时,全体团队在应对上会失落去灵巧性。例如:我们会担心业务数据块可能未做隔离,就被放到了池中,那么当一个实例“挂掉”时,所有的数据块都会受到影响。Pika,虽然可以利用硬盘,但是支配办法很少。Twemproxy,只是代理见长,其他的能力欠佳。后来,我们选择自己动手,做了一个 phoenix 的方案。全体系统包含了客户端、运维平台、以及存储扩容等方面。
在最初期的架构设计上,我们只让运用端通过大略的 SDK 去利用该系统。
为了避免做事端延续查找 Cache Server 的模式,我们哀求运用事先声明其项目和数据场景,然后给系统分配一个 Key。SDK 籍此为运用分配一个新的或既有的缓存仓库。
如上图所示,为了加快速率,我们将缓存区分出多个虚拟的逻辑池,它们对付上层调度系统来说便是一个个的场景。
那么运用就可以籍此申请包含须要存放何种数据的场景,末了根据所分配到的 Key 进行调用。
此处,底层是各种数据的复制和迁移,而两边则是相应的监控和运维。
但是在系统真正“跑起来”的时候,我们创造很难对其进行支配和扩容,因此在改造时,我们做重了全体缓存客户端 SDK,并引入了场景的配置。
我们通过进行本地缓存的管理,添加过滤条件,以担保客户端读取缓存时,能够知道详细的数据源和基本的协议,从而判断出要访问的是 Redis、还是 MemCache、或是其他类型的存储。
在 Cache 客户端做好之后,我们又碰到了新的问题:由于同程利用了包括 Java、.Net、Go,Node.js 等多种措辞的开拓模式,如果为每一种措辞都准备和掩护一套 Cache 的客户真个话,显然非常耗费人力。
同时,对付掩护来说:只假如程序就会有 Bug,只要有 Bug 就须要升级。一旦所有奇迹部的所有运用都要升级 SDK,那么对付所有嵌套运用的中间件来说,都要进行升级测试,这将会牵扯到巨大的回归量。
可以说这样的反复测试险些是不现实的。于是我们须要做出一个代理层,通过把协议、过滤、场景等内容下沉到 Proxy 中,以实现SDK的整体轻量化。
与此同时,我们在支配时也引入了容器,将全体 Redis 都运行在容器之中,并让容器去完玉成部运用的支配。
通过容器化的支配,集群的建立变得极其大略,我们也大幅丰富了集群的方案。
我们实现了为每个运用处景都能配有一个(或一种)Key,并且被一个(或一种)集群来做事。
众所周知,Redis 虽然实现了迁移扩容,但是其操作较为繁芜。因此我们自行研发了一套迁移调度系统,自动化地实现了从流量扩容到数据扩容、以及从纵向到横向的扩容。
如前所述,我们有着 Redis 和 Memcache 两种客户端,它们是利用不同的协议进行访问。因此,我们通过统一的 Proxy 来实现良好的支持。
如今在我们的缓存平台上,运维职员唯一须要做的便是:往该缓存平台里添加一台物理做事器、插上网线、然后系统就能够自动创造新的做事器的加入,进而开启 Redis。
而对付单场景下的 Redis 实例,我们也能够通过掌握台,以获取包括 Top10 的 Key、当前访问最多的 Key、Key 的属主、末了由谁实行了写入或修正等多个监控项。
可见,由于高下层都是自建的,因此我们扩展了原来 Redis 里没有的监控项。
上图是 Topkey 利用情形的一个示例,就像程序在故障时常常用到的 Dump 文件一样,它能够反响出后续的各种编排。
王晓波,同程艺龙机票奇迹群 CTO,专注于高并发互联网架构设计、分布式电子商务交易平台设计、大数据剖析平台设计、高可用性系统设计。 设计过多个并发百万以上平台。 拥有十多年丰富的技能架构、技能咨询履历,深刻理解电商系统对技能选择的主要性。
【51CTO原创稿件,互助站点转载请注明原文作者和出处为51CTO.com】