揭秘!双11万亿流量下的分布式缓存系统 Tair

简介:

Tair概览

Tair发展历程

Tair在阿里巴巴被广泛使用,无论是淘宝天猫浏览下单,还是打开优酷浏览播放时,背后都有Tair的身影默默支撑巨大的流量。Tair的发展历程如下:

  • 2010.04 Tair v1.0正式推出@淘宝核心系统;
  • 2012.06 Tair v2.0推出LDB持久化产品,满足持久化存储需求;
  • 2012.10 推出RDB缓存产品,引入类Redis接口,满足复杂数据结构的存储需求;
  • 2013.03 在LDB的基础上针对全量导入场景上线Fastdump产品,大幅度降低导入时间和访问延时;
  • 2014.07 Tair v3.0 正式上线,性能数倍提升;
  • 2016.11 泰斗智能运维平台上线,助力2016双11迈入千亿时代;
  • 2017.11 性能飞跃,热点散列,资源调度,支持万亿流量。

Tair是一个高性能、分布式、可扩展、高可靠的key/value结构存储系统!Tair特性主要体现在以下几个方面:

  • 高性能:在高吞吐下保证低延迟,Tair是阿里集团内调用量最大系统之一,双11达到每秒5亿次峰值的调用量,平均访问延迟在1毫秒以下;
  • 高可用:通过自动failover,限流,审计和机房内容灾以及多单元多地域,确保系统在任何情况下都能正常运行;
  • 规模化:分布全球各个数据中心,阿里集团各个BU都在使用;
  • 业务覆盖:电商、蚂蚁、合一、菜鸟、高德、阿里健康等。

Tair除了普通Key/Value系统提供的功能,比如get、put、delete以及批量接口外,还有一些附加的实用功能,使得其有更广的适用场景。Tair应用场景包括以下四种:

  1. MDB 典型应用场景:用于缓存,降低对后端数据库的访问压力,比如淘宝中的商品都是缓存在Tair中;用于临时数据存储,部分数据丢失不会对业务产生较大影响,例如登陆; 
  2. LDB 典型应用场景:通用kv存储、交易快照、安全风控等;存储黑白单数据,读qps很高;计数器功能,更新非常频繁,且数据不可丢失。
  3. RDB 典型应用场景:复杂的数据结构的缓存与存储,如播放列表,直播间等。
  4. FastDump 典型应用场景:周期性地将离线数据快速地导入到Tair集群中,快速使用到新的数据,对在线读取要求非常高;读取低延迟,不能有毛刺。

双 11 挑战怎么办?

d1c63c1c5b189653620ab9c0e252133c9af0c00c

2012-2017年数据如图,可以看到,2012年GMV小于200亿,2017年GMV达到1682亿,交易创建峰值从1.4万达到32.5万,峰值QPS从1300万达到近5亿。

从图中可以看出,tair访问增速远大于交易创建峰值,交易创建峰值也大于GMV的增长。也就是0点的那刻,对Tair来说,在保证高并发访问的同时,如何确保低延迟,如何确保成本低于业务增速的技术挑战越来越大。

对于分布式存储系统来说,热点问题都是比较难解决的。而缓存系统流量特别大,热点问题更为突出。2017年双11,我们通过了热点散列,彻底解决掉了缓存热点问题。

同时,为了承载每秒32.5万笔交易阿里的技术架构也不断演进成为多地域多单元的架构,不仅采用了阿里云上的单元,而且也有和离线服务混部的单元,这里对我们的挑战是如何快速弹性的部署和下线集群。

多地域多单元

d0e81fa6fdd8ccd03dd2baa43c72b6511121286d

先看下我们大致整体的部署架构和tair在系统中的位置。从这张简图上看到,我们是一个多地域多机房多单元的部署架构。整个系统上从流量的接入层,到应用层。然后应用层依赖了各种中间件,例如消息队列,配置中心等等。最底层是基础的数据层,tair和数据库。在数据这一层,我们需要为业务做需要的数据同步,以保障上层业务是无状态的。

多地域多单元除了防止黑天鹅之外,另外一个重要的作用是能够通过快速上线一个单元来实现承载部分的流量。Tair也做了一整套控制系统来实现快速的弹性建站。


弹性建站

b5a6697910819adf366164ea775693507f84c1fe

Tair本身是一个很复杂分布式存储系统,规模也非常庞大。所以我们有一个叫泰斗的运营管理平台。在这里面通过任务编排,任务执行,验证和交付等流程来确保快速的一键建站,离在线混部集群的快上快下工作。在部署工作完成后,会经过一系列系统,集群,实例上的连通性验证来确保服务完整无误后,再交付上线使用。如果有一丝遗漏,那么业务流量过来时,可能会触发大规模故障。这里面,如果是带数据的持久化集群,那么在部署完成后,还需要等待存量数据迁移完成并且数据达到同步后才能进入验证阶段。

Tair的每一个业务集群水位其实是不一样的,双11前的每一次全链路压测,由于业务模型的变化,所用Tair资源会发生变化,造成水位出现变化。在此情况下,我们每次都需要压测多个集群间调度的Tair资源。如果水位低,就会把某些机器服务器资源往水位高挪,达到所有集群水位值接近。

数据同步

82a40ce7dc3b8aff6d53d5097e932f25390e66ac

多地域多单元,必须要求我们数据层能够做到数据的同步,并且能够提供给业务各种不同的读写模式。对于单元化业务,我们提供了本单元访问本地Tair的能力,对于有些非单元化业务,我们也提供了更灵活的访问模型。同步延迟是我们一直在做的事情,2017年双11每秒同步数据已经达到了千万级别,那么,如何更好地解决非单元化业务在多单元写入数据冲突问题?这也是我们一直考虑的。

性能优化降成本

服务器成本并不是随着访问量线性增长,每年以百分之三四十成本在下降,我们主要通过服务器性能优化、客户端性能优化和不同的业务解决方案三方面达到此目标。

先来看下我们如何从服务端角度提升性能和降低成本的。这里的工作主要分为两大块:一块是避免线程切换调度,降低锁竞争和无锁化,另外一块是采用用户态协议栈+DPDK来将run-to-completion进行到底。

内存数据结构

97af31687e5a2c4c52fe4a0700d2b18756437efb

MDB内存数据结构示意图

我们在进程启动之后会申请一大块内存,在内存中将格式组织起来。主要有slab分配器、hashmap和内存池,内存写满后会经过LRU链进行数据淘汰。随着服务器CPU核数不断增加,如果不能很好处理锁竞争,很难提升整体性能。

b7df46f8c65f83408caad28df2ffc859423b952f

通过参考各种文献,并结合tair自身引擎需求,我们使用了细粒度锁、无锁数据结构、CPU本地数据结构和RCU机制来提升引擎的并行性。左图为未经过优化时各个功能模块的CPU消耗图,可以看到网络部分和数据查找部分消耗最多,优化后(右图)有80%的处理都是在网络和数据的查找,这是符合我们期望的。

用户态协议栈

2ed20baab6216300b64cd50e2cb89d70ba0f9662

锁优化后,我们发现很多CPU消耗在内核态上,这时我们采用DPDK+Alisocket来替换掉原有内核态协议栈,Alisocket采用DPDK在用户态进行网卡收包,并利用自身协议栈提供socket API,对其进行集成。我们将tair,memcached以及业内以性能著称的seastar框架相比,tair的性能优势在seastar 10%以上。

内存合并

cde8d4fa28c4252cefc25b4132204f7dc2f5bbb6

当性能提升后,单位qps所占用的内存就变少了,所以内存变得很紧缺。另外一个现状,tair是一个多租户的系统,各个业务行为不太一样,时常会造成page已经分配完毕,但是很多slab里的page都是未写满的。而有少量slab确实已经全占满了,造成了看上去有容量,但无法分配数据的情况。

此时,我们实施了一个将同一slab里未写满page内存合并的功能,可以释放出大量空闲内存。从图中可以看到,在同一个slab里,记录了每个page的使用率,并挂载到不同规格的bucket上。合并时,将使用率低的page往使用率高的page上合并。同时还需要将各个相关联的数据结构,包括LRU链,相当于整个内存结构的重整。这个功能在线上的公用集群里效果特别好,根据不同场景,可以大幅提升内存使用效率。

客户端优化

7930ddcecf7575bbad74e42db160ed387459d94a

上面这些是服务端的变化,接下来看看客户端的性能。我们的客户端是运行在客户服务器上的,所以占用了客户的资源。如果能尽可能低的降低资源消耗,对我们整个系统来说,成本都是有利的。客户端我们做了两方面优化:网络框架替换,适配协程,从原有的mina替换成netty,吞吐量提升40%;序列化优化,集成 kryo和hessian,吞吐量提升16%+。

内存网格

5ee979d74d4ea6247613f8b1bea65a0cb6d9f4a6

如何与业务结合来降低整体Tair与业务成本?Tair提供了多级存储一体化解决业务问题,比如安全风控场景,读写量超大、有大量本地计算,我们可以在业务机器本地存下该业务机器所要访问的数据,大量读会命中在本地,而且写在一段时间内是可合并的,在一定周期后,合并写到远端Tair集群上作为最终存储。我们提供读写穿透,包括合并写和原有Tair本身具有多单元复制的能力,双11时业务对Tair读取降至27.68%,对Tair写入降至55.75%。

热点难题已解决

缓存击穿

113e33128acbcbab540fcd7612ec390d0d319ce7

缓存从开始的单点发展到分布式系统,通过数据分片方式组织,但对每一个数据分片来说,还是作为单点存在的。当有大促活动或热点新闻时,数据往往是在某一个分片上的,这就会造成单点访问,进而缓存中某个节点就会无法承受这么大压力,致使大量请求没有办法响应。对于缓存系统一个自保的方法是限流。但是限流对于整个系统来说,并无济于事。限流后,一部分流量会去访问数据库,那依然和刚刚所说的无法承受是一样的结果,整个系统出现异常。

所以在这里,唯一的解决办法是缓存系统能够作为流量的终结点。不管是大促,还是热点新闻,还是业务自己的异常。缓存都能够把这些流量吸收掉,并且能让业务看到热点的情况。

热点散列

c5e909d2575e961516f775f6c763d1b5a38ea58e

经过多种方案的探索,采用了热点散列方案。我们评估过客户端本地cache方案和二级缓存方案,它们可以在一定程度上解决热点问题,但各有弊端。例如二级缓存的服务器数目无法预估,本地cache方案对的业务侧内存和性能的影响。而热点散列直接在数据节点上加hotzone区域,让hotzone承担热点数据存储。对于整个方案来说,最关键有以下几步:

  • 智能识别。热点数据总是在变化的,或是频率热点,或是流量热点。内部实现采用多级LRU的数据结构,设定不同权值放到不同层级的LRU上,一旦LRU数据写满后,会从低级LRU链开始淘汰,确保权值高的得到保留。
  • 实时反馈和动态散列。当访问到热点时,appserver和服务端就会联动起来,根据预先设定好的访问模型动态散列到其它数据节点hotzone上去访问,集群中所有节点都会承担这个功能。

通过这种方式,我们将原来单点访问承担的流量通过集群中部分机器来承担。

79690ccda6c869b06f5958ecd0de2955eadf3a2b

整个工程实现是很复杂的,热点散列在双11中取得了非常显著的效果。峰值每秒吸收了800多w的访问量。从右图可以看到,红色的线是如果未开启热点散列的水位,绿色的线是开启热点散列的水位。如果未开启,很多集群都超过了死亡水位,也就是我们集群水位的130%。而开启之后,通过将热点散列到整个集群,水位降低到了安全线下。换而言之,如果不开启,那么很多集群都可能出现问题。

写热点

30d5fdc54938e8c7e9d3e3bd70c7789cf8236add

写热点与读热点有类似的地方,这块主要是通过合并写操作来实施。首先依然是识别出热点,如果是热点写操作,那么该请求会被分发到专门的热点合并线程处理,该线程根据key对写请求进行一定时间内的合并,随后由定时线程按照预设的合并周期将合并后的请求提交到引擎层。通过这种方式来大幅降低引擎层的压力。

经过双11考验对读写热点的处理,我们可以放心的说,Tair将缓存包括kv存储部分的读写热点彻底解决了。



原文发布时间为:2018-02-28

本文作者:宗岱

本文来自云栖社区合作伙伴“阿里技术”,了解相关信息可以关注“阿里技术”微信公众号

相关文章
|
3月前
|
存储 机器学习/深度学习 缓存
一看就懂!图解 Kotlin SharedFlow 缓存系统
一看就懂!图解 Kotlin SharedFlow 缓存系统
69 2
|
19天前
|
缓存 NoSQL Java
手撸的 SpringBoot缓存系统,性能杠杠的
手撸的 SpringBoot缓存系统,性能杠杠的
27 0
|
1月前
|
消息中间件 存储 NoSQL
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
|
1月前
|
缓存
LRU 缓存置换策略:提升系统效率的秘密武器(下)
LRU 缓存置换策略:提升系统效率的秘密武器(下)
|
1月前
|
存储 缓存 算法
LRU 缓存置换策略:提升系统效率的秘密武器(上)
LRU 缓存置换策略:提升系统效率的秘密武器(上)
|
1月前
|
存储 Web App开发 运维
原来10张图就可以搞懂分布式链路追踪系统原理
原来10张图就可以搞懂分布式链路追踪系统原理
|
1月前
|
算法 Java 数据中心
分布式ID生成系统之雪花算法详解
在当今的云计算和微服务架构盛行的时代,分布式系统已成为软件开发的重要组成部分。随着系统规模的扩大和业务的复杂化,对数据一致性和唯一性的要求也越来越高,尤其是在全局唯一标识符(ID)的生成上。因此,分布式ID生成系统应运而生,成为保证数据唯一性和提高系统可扩展性的关键技术之一。雪花算法(Snowflake)是Twitter开源的一种算法,用于生成64位的全局唯一ID,非常适用于分布式系统中生成唯一标识符。下面我们将深入探讨雪花算法的原理、结构和实现方式。
92 2
 分布式ID生成系统之雪花算法详解
|
2月前
|
存储 缓存 Python
如何在Python中实现一个简单的缓存系统?
如何在Python中实现一个简单的缓存系统?
27 1
|
2月前
|
存储 缓存 算法
使用Java实现高效的数据缓存系统
【2月更文挑战第3天】在大规模的应用程序中,数据缓存是提高应用程序性能的一种重要方法。本文介绍了如何使用Java实现高效的数据缓存系统。我们将讨论缓存的设计原则和缓存算法的选择,同时详细说明如何使用Java内置的缓存库和其他开源工具来构建一个可靠、高效的数据缓存系统。
|
2月前
|
缓存 NoSQL Go
从2开始,在Go语言后端业务系统中引入缓存
从2开始,在Go语言后端业务系统中引入缓存
29 0