缓存+存储的系统架构是目前常见的系统架构,缓存层负责加速访问,存储层负责存储数据。这样的架构需要业务层或者是中间件去实现缓存和存储的双写、冷热数据的交换,同时还面临着缓存失效、缓存刷脏、数据不一致等问题。本文是对腾讯云数据库高级产品经理邹鹏老师在「腾讯云开发者社区沙龙online」的分享整理,希望与大家一同交流
在互联网和移动互联网两波浪潮的推动下,存储技术有了飞速发展。移动互联网用户在过去十年增长了10倍,用户的增长带动了数据量的指数级增长,因为激烈的市场竞争,企业和用户对应用程序的响应性能要求越来越高,在完美应对庞大的用户规模和海量数据集的同时保证优秀的产品体验,是数据库面临的挑战。
在机械硬盘普及的时代,企业需要通过缓存技术加速数据的访问,在SSD存储介质普及后,企业需要缓存技术支撑高并发和大吞吐,通过引入分布式缓存方案,提升应用程序性能,消除数据库热点。
但是缓存技术的引入增加了业务架构的复杂度,降低了开发效率,同时还面临着缓存一致性、缓存击穿、缓存雪崩等挑战。腾讯云数据库团队推出的Redis混合存储产品,融合缓存和存储的统一架构,彻底解决了缓存难题,帮助企业的研发人员聚焦业务逻辑,提升生产效率。
缓存一致性是指业务在引入分布式缓存系统后,业务对数据的更新除了要更新存储以外还需要同时更新缓存,对两个系统进行数据更新就要先解决分布式系统中的隔离性和原子性难题。
目前大多数业务在引入分布式缓存后都是通过牺牲小概率的一致性来保障业务性能,因为要在业务层严格保障数据的一致性,代价非常高,业务引入分布式缓存主要是为了解决性能问题,所以在性能和一致性面前,通常选择牺牲小概率的一致性来保障业务性能。
缓存击穿是指查询请求没有在缓存层命中而将查询透传到存储DB的问题,当大量的请求发生缓存击穿时,将给存储DB带来极大的访问压力,甚至导致DB过载拒绝服务。空数据查询(黑客攻击)和缓存污染(网络爬虫)是常见的引发缓存击穿的原因。
什么是空数据查询?空数据查询通常指攻击者伪造大量不存在的数据进行访问(比如不存在的商品信息、用户信息)。
缓存污染通常指在遍历数据等情况下冷数据把热数据驱逐出内存,导致缓存了大量冷数据而热数据被驱逐。缓存污染的场景我们目前还没有发现较好的解决方案,但是在空数据查询问题上我们可以改造业务,通过以下方式防止缓存击穿:
缓存雪崩是指由于大量的热数据设置了相同或接近的过期时间,导致缓存在某一时刻密集失效,大量请求全部转发到DB,或者是某个冷数据瞬间涌入大量访问,这些查询在缓存MISS后,并发的将请求透传到DB,DB瞬时压力过载从而拒绝服务。
目前常见的预防缓存雪崩的解决方案,主要是通过对key的TTL时间加随机数,打散key的淘汰时间来尽量规避,但是不能彻底规避。
在引入分布式缓存后,我们的业务架构由原有两层架构(应用+数据库)变成了三层架构(应用+缓存+存储),缓存层缓存热数据,存储层负责全量数据持久化存储。
存储架构的变化要求业务对数据的存取逻辑进行相应调整,而且这个调整是巨大的。在缓存系统的选择上,常见的缓存数据库包括Memcached、Redis,目前使用最广泛的是Redis,存储数据常见的包括关系型数据库MySQL、PG、Oreacle、SQLServer等,NoSQL数据库MongoDB、Hbase等。
在引入分布式缓存后,业务逻辑需要做三个点的变化,缓存读取、缓存更新、缓存淘汰。
引入缓存层后,读数据就变得不是那么简单直接了,APP需要先去缓存读取数据,如果缓存MISS(数据没有被缓存),则需要从存储中读取数据,并将数据更新到缓存系统中,整个流程和代码如下所示:
# Python
def get_user(user_id):
# Check the cache
record = cache.get(user_id)
if record is None:
# Run a DB query
record = db.query("select * from users where id=?",user_id)
# Populate the cache
cache.set(user_id, record)
return record
# App code
user = get_user(17)
示例代码
我们把常见的缓存更新方案总结为两大类,业务层更新和外部组件更新,比较常见的是通过业务更新的方案。
刚开始接触缓存方案的同学可能会纠结几个点,先更新缓存还是先更新存储,缓存的处理是通过删除来实现还是通过更新来实现。
这里我们面临的问题本质上是一个数据库的分布式事务的问题,需要处理数据可靠性的挑战,并发更新带来的隔离性挑战,和数据更新原子性的挑战。
数据可靠性:如果要保证数据的可靠性,在业务逻辑成功之前,必须保障有一份数据落地,我们有以下两个选择:
操作隔离性:一条数据的更新涉及到存储和缓存两套系统,如果多个线程同时操作一条数据,并且没有方案保证多个操作之间的有序执行,就可能会发生更新顺序错乱导致数据不一致的问题。
更新原子性:引入缓存后,我们需要保证缓存和存储要么同时更新成功,要么同时更新失败,否则部分更新成功就会导致缓存和存储数据不一致的问题。
我们看到大多数的常见是选择以下方案,保障数据可靠性,尽量减少数据不一致的出现,通过TTL超时机制在一定时间段后自动解决数据不一致现象。
Step1:更新存储,保证数据可靠性;
Step2:更新缓存,2个策略怎么选:
积极更新策略,缓存数据实时性更高,但是在缓存侧带来了更多的更新操作,这会提高更新冲突导致脏数据概率。
a. 缓存MISS处理方案
在通过第三方组件更新的方案中,为了保障数据的一致性,避免对单条数据的并行更新,缓存的所有更新操作都需要交给同步组件,因此缓存MISS场景下的逻辑
b. 缓存更新方案
先更新存储,由第三方组件异步更新缓存。该方案投入较大,只适合特定的场景,并且有以下3个难点:
c. 其他缓存更新方案
在实际的生产中,我们还会看到很多先更新缓存,然后通过第三方组件更新存储的场景,但是这个方案也会面临数据一致性和数据可靠性的挑战,虽然不推荐,但是确实还是能看到有在使用这个方案的,我们拿出来探讨下。
缓存的作用是将热点数据缓存到内存实现加速,内存的成本要远高于磁盘,因此我们通常仅仅缓存热数据在内存,冷数据需要定期的从内存淘汰,数据的淘汰通常有两种方案:
主动淘汰:这是推荐的方式,我们通过对Key设置TTL的方式来让Key定期淘汰,以保障冷数据不会长久的占有内存。TTL的策略可以保证冷数据一定被淘汰,但是没有办法保障热数据始终在内存,这个我们在后面会展开;
被动淘汰:这个是保底方案,并不推荐,Redis提供了一系列的Maxmemory策略来对数据进行驱逐,触发的前提是内存要到达maxmemory(内存使用率100%),在maxmemory的场景下缓存的质量是不可控的,因为每次缓存一个Key都可能需要去淘汰一个Key。
腾讯云Redis混合存储版基于腾讯游戏线上运营多年的Tendis引擎打造。数据自动降冷,落盘压缩,最大可降低成本85%,100% 兼容Redis协议,可助力企业大幅提升生产效率,降低运营成本。
a. 混合存储解决方案
b. 解决缓存三大难题
c. 100%兼容Redis协议
d. 超高读写性能
a. 数据自动降冷
b. 精准缓存
c. 数据压缩
a. PB级KV存储解决方案
b. 高扩展性
Redis混合存储版架构核心组件由Proxy、缓存Redis、存储Tendis组成,其中每个组件的功能介绍如下:
Proxy组件:负责对客户端请求进行路由分发,将不同的Key的命令分发到正确的分片,同时Proxy还负责了部分监控数据的采集,以及高危命令在线禁用等功能。
缓存Redis:缓存Redis组件源自于Redis 4.0 Cluster,为了支持冷数据自动降冷,我们对Redis进行了Value淘汰、写入数据同步Tendis、冷数据访问、主备热数据同步、按时间淘汰Value等核心功能的改造,改造后的混合存储版100%兼容Redis Cluster命令。
存储Tendis:Tendis是腾讯自研的KV存储引擎,一个兼容Redis协议的Rocksdb存储引擎,该引擎已经在腾讯集团内部运营多年,性能和稳定性得到了充分的验证。在混合存储系统中主要负责全量数据的存储和读取,以及数据备份,增量日志备份等功能。
缓存所有Key 和 热数据Value,在缓存层拦截空数据查询,避免无效查询透传;
可控的冷数据缓存策略,提供可配置的了冷数据缓存配置,例如业务可以配置在5分钟内访问次数超过3次才缓存在内存,可以有效防御缓存污染。
前面介绍到,缓存雪崩主要在两种情况会触发:
第一:大量热Key同时被淘汰,其中到原因是TTL设置时间接近。Redis混合存储版支持动态TTL,每次对Key的访问都会触发TTL更新,保障热数据持久缓存,有效规避缓存密集淘汰,我们通过两个参数配置来实现动态TTL:
第二:一个冷Key瞬间产生大量的访问,由于缓存MISS导致大量请求透传到存储层,Redis混合存储版通过合并冷数据缓存的请求,同一个key的请求只访问一次存储层(Ten第三),可以将对存储层的访问降到最低,从而避免存储层过载。
Redis混合存储版已在腾讯云正式上线,扫描下方二维码即可进入免费试用申请页面(目前仅对腾讯云官网注册的企业账户开放申请)。
Q:缓存击穿和缓存穿透有什么区别?
A:缓存击穿和穿透,我们可以理解为缓存层失效了,压力都到了存储层。
Q:还招人么?
A:在大量招,如果有对 Redis 了解的同学更加欢迎。请投至邮箱:lynzou@tencent.com
Q:接入这套解决方案多少钱?看来不是开源的,有计划没?
A:目前是在公有云公开售卖,能看到价格。我们也给大家提供了一个数字,最大可比用纯内存版节省成本 85%。后续有计划做开源,因为这块技术后续会往这个方向发展。
Q:Redis本身的内存及CPU消耗是个什么水平?
A:我把这个问题理解成 Redis 混合存储版对CPU和内存的消耗。你可以把它理解成现在我们混合群组里面缓存这一层的 Redis,会比原来纯 Redis 的消耗高一点点,因为涉及到了在里面引入revision。还有它的缓存和淘汰逻辑会更复杂,因为在这一个层面我们会去做缓存淘汰,所以光Redis 这个层面性能相对纯内存会有所下降。
我们目前能够看到,整个系统的稳定性能大概是在2万到3万,极限压测能够到四五万的样子,但是一旦到高负载压测的时候,磁盘的问题就出现了,在压到三万以上的时候就很可能会出现抖动,抖动带来的问题就是时延加大,甚至写入失败。
Q:数据压缩是否带来CPU的开销影响效率?
A:这是必然的。我们在空间和时间上面肯定都有换算。
目前来看,我们看到压缩的性能消耗还是其次的, Tendis 的主要开销除了传统的写入。RocksDB有一个compaction,它所有的请求都会顺序写入,然后再分级进行合并。这个合并的开销是蛮大的,合并的目的是为了合并存储空间。
所以大家在使用这个版本的时候,可能会碰到磁盘空间是一个曲线不停的在变化,一会大一会小。在写入的时候肯定会变大,过一会儿又会自动变小。这是因为 compaction 在合并时候的一个机制。
Q:这个和Redis本身处理空查询有什么区别?
A:Redis 本身没有处理空查询的能力,通常需要业务去使用布隆过滤器等方案来处理空查询。
目前有一些的业务方案是:如果查到一个缓存是空的,我们把key丢到缓存中,把它置为一个空的value,下一次查询时会查询是命中,并且返回的值是空的,这样就可以解决这个问题,但是需要业务层去开发写逻辑才可以,而我们直接就把这部分的事情做了。
Q:请问冷数据value为空(只缓冲key), 与该key的真实value为空,两者如何判断?
A:我们在做开发的时候有一个逻辑,会有一个标识位,在Redis 中通过这个标识位就可以判断是被淘汰了还是为空。
Q:老师能不能介绍一下备份机制呢?
A:目前混合存储板的主要场景还是存储,这个版本的备份机制是通过RocksDB来实现的。所以备份的是RocksDB 的SST文件。这个备份和MySQL的备份机制是一样的,备份全量的数据和增量的日志。所以混合存储板可以像MySQL一样支持按时间点回档,并且还有一个好处。很多数据库在备份的时候会先锁个表把全量的数据拷出来,不管是用逻辑还是物理的方式,然后再去备份增量的数据。这样备份很耗性能和空间。但RocksDB有一个好处,就是它每一次的写入都是增量的,做全量备份的时候直接切一个快照,然后把在这个点之前的文件全部直接拖走就可以,基本上是秒级就可以实现全量备份。全量备份和存量备份结合起来就可以实现任意时间点的回档。
Q:Redis混合存储适用于哪些应用场景,哪些场景不适用?
A:两个推荐:
需要自带缓存加速的KV存储场景,比如Redis+MySQL的架构,但是你又不需要MySQL的复杂查询,仅仅是通过MySQL实现数据可靠性存储;
分布式大容量KV存储场景,TB甚至数百TB级别的分布式KV存储方案。
两个不推荐:
纯缓存场景,混合存储由于引入磁盘引擎,冷数据的访问在高负载情况下可能会存储时延大的问题,没有办法像内存版Redis一样保证稳定的访问时延,同时这个版本的性能要明显的低于内存版本,特别是写入性能会收到磁盘的限制;
计算密集场景,如果你是计算密集的场景,并且数据不存储冷热分界,这个也不推荐。
Q:前面说Redis数据有版本号,如果没有落地就不会失效,防止查询存储层的脏数据。如果这个时候Redis出故障呢?数据丢了呢?
A:这个问题不是一致性的问题,而是可靠性的问题。Redis混合存储版在可靠性方面有两个可选选项。
第一个性能优先是优先,先写缓存,异步去写Tendis。如果这个时候Redis挂了,并且Redis 的从机没有收到最新的数据,这个时候就可能会丢失数据。这就和MySQL异步复制一样,会存在没有同步的数据被丢掉。所以这种场景下的可靠性和MySQL异步复制是一样的。
接下来还会提供一个版本,就是每一次写入的数据后会更新Tendis,Tendis更新OK之后会返回ACK,Redis收到ACK之后才会告诉应用程序更新成功。如果更新Redis成功、更新Tendis失败,就会把更新的数据失效掉。这样就能保证缓存和存储在可靠性上能够做到一致。
Q:在访问缓存层和数据层之前将存在的key用布隆过滤器提前保存起来,做第一层拦截,但是代码维护感觉复杂 有什么别的方案吗?(千万级数据集)
A:空数据查询的时候,通常使用的一个方案是在前面加过滤器,用过滤器拦截掉不存在的key。Redis混合存储版会在内存里面缓存所有的key,空数据在缓存的时候就直接被拦截了,不会到达存储层,这是我们现在的一个解决方案。
Q:灾备是怎么处理的,全依靠腾讯云吗?
A:腾讯云的Redis内存板正在做一个全球同步的版本,无论你的灾备异地只读还是多活都能够支持,在内存版之后,也会逐步去支持混合存储版,也就是把代码同步到混合存储版,到时候混合存储版也能做到存储的灾备、异地的灾备以及异地多活的架构。
Q:是否可以自建机房,对服务器有什么要求,必须是固态硬盘吗?
A:这个问题我理解为混合存储版能不能够支持私有化的部署。后面我们是有私有化部署的计划,在公有云上我们的版本成熟后,就会往专有云的版本去输出。对于是否必须是固态硬盘,目前来看主要是取决于业务需求,如果性能要求高,肯定要求固态硬盘,如果对性能要求不高,传统的机械磁盘也OK。
Q:关于分布式锁咱们的产品有自己的实现方式么?
A:现在大部分都是用redlock用的多,更优雅的是用CAS的原子命令去实现。后面我们也会去支持CAS相关的原子命令。
Q:cache节点是单点吗,cache节点故障检测/容忍和恢复的细节过程是什么?
A:首先,cache节点不是单节点,是个主备的架构。而且我们会支持一组多备去扩展读性能的架构。节点故障的检测/容忍和恢复的细节,Redis是一个Redis cluster,本身自己会进行简单的检查,外部也会有一些机制,比如check物理机或者内存块去检查。和纯内存的Redis高可用架构是一样的。
这里还有一个细节,因为这个版本我们引入了Tendis,所以在Redis HA的时候,我们会去check存储和缓存的数据的版本,有可能会从存储里面去刷数据,也就是我们的缓存版本会低于存储的版本。
Q:Redis更新后什么时候发起Tendis的异步更新,cache节点故障下,在还没有更新Tendis情况下,数据可能丢失吗?
A:Redis 的更新目前是异步更新到Tendis。目前数据的可靠性有两个版本,第一个是我们刚才介绍到的性能优先的版本,它是异步更新的。如果Redis故障并且数据没有刷到Tendis,而且从机也没有复制这个数据,那么这个数据是会被丢掉。原理就等价于MySQL的异步复制,保证数据的可靠性。
Q:同步组件实现目前选择什么方案?
A:目前的同步组件是自研的一个方案,大致的原理是从AOF复制数据,再把一些数据拆分同步到Tendis。
Q:请问一个更新操作在进行到哪一步会向客户端返回成功?主从架构下,数据同步到slave是异步的吗?如果数据还未同步到slave而master宕机,数据是否有丢失风险?
A:异步复制的版本中,只要写入成功之后就会直接返回,如果是同步复制的逻辑,会在更新Redis并且更新Tendis成功之后,才会返回。
Q:Redis的内存大小和tendis的磁盘大小,两者比例是多少?是否可以配置?
A:目前我们提供的是一个分布式的,也就是多分片的一个架构。Redis的内存和Tendis 的磁盘两者是可配的,而且是可调整的。这个配置是自己可调的,可以在腾讯的控制台直接拖你的磁盘,选择什么样的规格。
Q:compaction的过程会不会出现短暂的相应延时较高?
A:如果存储引擎写入的负载非常高就会出现这种情况。虽然会有一定的延时,但compaction在聚合的时候非常耗CPU,如果层级上下层的比例很大时,它一定回去强行触发合并,这是可能就会短暂的出现延时较高的问题。
Q:如何做到只查cache就知道key也不在下层存储里面?
A:我们会在缓存层里面把所有的key都给存储起来,这样相当于一个过滤器,你的key全部存在缓存就知道了,不用再去查存储。
Q:值是永久存储吗?即使在Redis中过期了
A:过期这个概念需要简单介绍一下。在混合存储版中TTL的语义没有发生改变,TTL到期或者仍然会删除数据,所以说这个版本的正确使用姿势就是,不需要删除的key就不要去设expire,不然你的数据会被删除,我们会在缓存层自动做一个缓存的调配,但是数据是持久化的磁盘,这一点是一个区别。
Q:公有云上,如果是典型的常规网管类应用(设备接入、认证、配置&管理、报表、关联分析),用传统的分布式数据库更有优势还是Redis更有优势?
A:Redis 主要是在互联网产品非结构化的数据和科学存储,它能够支持非常轻量的范围查询。但我理解像网管类的应用设备,还是更偏传统的关系型数据库。Redis还是偏互联网场景,更能够支持的设备都是带模式的表结构的数据库。
Q:大key存储方面有哪些限制?
A:大key存储目前没有限制,但是会在初期的版本上设置一个阈值,就是大于8M的一个value,我们暂时不会把它从内存驱逐。因为我们从把它存储层面捞起来的时候,整个耗时会非常长,也会明显的影响我们的性能。针对这个场景我们后面会优化一个大key场景的缓存,会把结构复杂的key穿透到存储层解决。
Q:现在Redis混合存储版SLA能达到几个9?日常烧内存或SSD损坏频率高吗?
A:混合存储版的SLA和其他存储数据库都是一样的,我们可用性的SLA是三个9,一个月99.95%。
日常烧内存还好,内存主要是SSD,但我们现在用的是分布式的存储,所以这一块暂时不是我们维护。SSD我觉得它有一个优势,就是我们这个版本因为是顺序写入的,肯定比随机写要好一点,IO的频率会低一些。从这个角度来说,它应该会比其他的数据库对SSD的保护更好一些。
Q:哨兵模式不能启用吗?
A:如果选用Redis混合存储版,直接把哨兵的逻辑去掉就可以了。我们会提供一个VIP,直接把它当成单机版使用就可以。
Q:Redis如何保障多中心双活?部署架构是什么样的?
A:我们会提供灾备和多AZ部署的方案。灾备的方案就是在多个地域或者多个AZ去部署单独实例,通过全球复制的功能实现灾备。多中心双活,我们后面会提供一个全球多活这样一个可以多点写入的方案,可以在后面关注我们产品上线的一个进度。
Q:大量数据过期,后台会自动清理吗?
A:如果是混合存储版设置了一个过期的时间,到时间之后后台就会把key删除掉,在缓存和存储同时删除。
Q:范围扫描的操作,性能如何?
A:如果执行的命令不涉及到value,它的性能就和内存版是一样的,如果涉及到value,其实就是磁盘版的性能。
Q:刚提到计划全球同步,面临最大的挑战是什么?个人认为物理延迟是无法避免的,强一致是很难的,物理链路决定了延时。那是否只能保证最终一致性?
A:我们在全球同步的时候,比如国内和海外有100甚至200、300毫秒的时延,如果要求强一致业务肯定是不能接受的。
这个问题最终还是要回归到用户的真实场景,假如在全球同步全球多活的场景下,可能会有异地读的需求。比如游戏的排行榜,玩家在国内刷新数值,会立即更新到国内的排行榜,同时也会异步更新到海外的Redis,数据可能会有一点延迟,但最终大家看到排行榜的数据是一致的。
这种场景对数据实时性的要求肯定不是特别高,如果真的特别高就不能够选择这种方案。所以一般是由业务进行选择,业务如果接受全球复制,那么一定能够接受数据的延迟。
Q:用了这个,是不是就没有必要在用关系型数据库像MySQL?
A:这个是我们设计这个产品的一个初衷,因为我们发现很多使用Redis+MySQL的场景,MySQL的作用仅仅是为了保障数据的可靠性,这种场景下我们使用一个混合存储就够了。或者是我们使用MySQL的目的是为了数据落地和做离线的分析,那可以把这个拆开,线上业务使用混合存储版保障业务逻辑简单,存储性能足够高,线下的分析的数据存储在MySQL。但是这个方案不是万能的,因为Redis是一个NoSQL数据库,没有完整的事务支持,不能支持复杂的查询操作,所以最佳的时间是将非结构化的数据存储在混合存储版,结构化数据存储在关系型数据库。
Q:是基于k8s吗,怎么暴露服务的?k8s的网络有优化吗?
A:架构不是基于k8s,暴露服务是一个VIP,每一个实例每一个集群会提供一个vip,像操作单机版一样使用。
Q:集群版是社区版cluster的还是proxy的,性能损失多少?
A:集群版是proxy加cluster的架构,但Redis我们改造的点还蛮多的。因为要解决数据一致性,淘汰value等。
混合存储版本,如果他和纯内存的Redis版本相比,冷数据的读写性能一定是磁盘的性能,而不是Redis的性能,磁盘的性能和Redis的性能不是一个量级的。目前单分片的磁盘性能大概是2-3万,极限压测能够达到4万多的情况。但纯内存版的随便可以跑到10万,所以是有明显的下降,还有时延的问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。