前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于分布式“缓存”的思考

关于分布式“缓存”的思考

原创
作者头像
郝阳
修改2017-08-10 09:30:27
2.7K1
修改2017-08-10 09:30:27
举报
文章被收录于专栏:郝阳的专栏郝阳的专栏

一 缓存的划分

从由谁来维护缓存的角度去划分Cache-Aside模式和Cache-Proxy模式。

1 .调用方自己维护缓存(Cache-Aside模式)

调用方的伪代码:

代码语言:javascript
复制
value = get_from_cache(key);
if (value == null) {
    value = get_value_from_database(key);
    set_cache(key, value)
}

业务调用方自己感知缓存的状态,如果命中则从缓存中取,不命中则从DB拉取数据,并更新缓存。

1)In-Memory(本地私有缓存):

本地缓存的模式下,get_from_cache是本地函数调用(无网络交互),尝试从本地cache中获取缓存数据;如果命中则返回数据,不命中则从DB拉取数据后,更新本地的缓存。

2)Shared-Cache(远端共享存储):

远端缓存的模式下,get_from_cache是RPC调用(需网络交互),尝试从memcached、redis等缓存中间件中拉取缓存数据;如果命中则返回数据,不命中则从DB拉区数据之后,更新远端的缓存。

In-Memory VS Shared-Cache:

1)In-Memory模式,效率更高,少网络交互,但是容易出现数据不一致。如果A和B本地都缓存了相同的数据,那么A更新之后,如果通知B更新,就变成了一个比较复杂的问题。几个参考方案可。

● A和B按照号段划分数据,各自缓存不同的数据,处理不同的请求。这样可以保证自己的缓存数据不会与别人的冲突,避免了复杂的一致性问题;

● 有些情况(例如单号段的请求量非常大)A和B必须缓存相同的数据,可以考虑引入一些简单的一致性策略。例如参考微信账号体系的缓存方式,引入远端版本号服务器解决问题等(参考文献2《微信账号体系的缓存解决方案》)。

2)Shared-Cache模式,需要网络交互,但是数据一致性相对容易实现(当然仔细考虑下,如果要完美实现也不是很容易的)。相对而言,访问cache的效率低一些;网络流量较大;需要考虑网络超时等异常和操作相同节点的并发情况。

2.委托缓存代理侧维护(Cache-Proxy模式)

调用方的伪代码简化为:

代码语言:javascript
复制
value = get_from_cache_proxy(key);

Cache-Proxy(也称之为Cache-Service),带来的好处如下:

● 对调用方隔离了DB操作。在调用方看来,访问Cache-Proxy就实现了访问DB的能力,不关心更新缓存的细节; ● 调用方可以简化调用协议。通常更新缓存和请求DB的协议不同,而这个协议转换的工作,完全交给了Cache-Proxy去实现; ● 各种缓存调整对调用方透明(隔离)。选择不同的淘汰策略算法、调整缓存大小、命中率等等。

从读取和更新缓存的方式去划分:

1)Read-Through:如果读不命中缓存,那么就从DB加载数据,并更新缓存; 2)Write-Through:更新缓存的同时去更新DB,每次更新都触发写DB(类似写文件,每次都sync到磁盘); 3)Write-Behind:更新缓存并不触发更新DB,而是延迟一段时间再写回DB(类似写文件,累计一定的修改之后,一起写回磁盘)。

Write-Through VS Write-Behind: 1)Write-Through模式:没有脏数据,Cache的数据始终与DB的数据一致。但由于写请求都直接透传到DB,所以DB的亚历山大!采用这个模式,要充分评估好DB侧的负载。 2)Write-Behind模式:有脏数据,Cache的数据与DB不一致。但是该模式可以很好的降低DB侧的写请求,平滑负载。但是如果Cache侧宕机或者Crash,造成的就是数据丢失。所以如何保证数据一致性,采用什么策略回写数据到DB,都是需要好好考虑的问题。

关于缓存的更新也有挺多细节可以好好考虑下,陈皓的《缓存更新套路》中,介绍了一些,可以参考下(参考文献1.1《缓存更新的套路》)。

二 缓存的主从同步

(以下针对Cache-Proxy模式讨论)

缓存是否需要主从备份?

可以从两个因素来判断缓存是否需要同步: 1.数据一致性。如果采用了Write-Beind模式,那么Cache就会有脏数据,如果此时没有主从备份,那么Cache所在进程或服务器出问题,就会造成脏数据丢失,数据回档。这种数据回档的情况业务是否容忍? 2.可用性。即使采用Write-Through模式,或者Cache仅仅是用于读请求,如果Cache侧挂掉,服务是否会受影响而中断?这种业务中断是否容忍?

缓存如何进行主从同步?

之前写过一篇关于主从同步的文章《主从同步中的关键技术解析》,对比介绍了Redis、Mysql、TCaplus的主从同步中用到的关键技术。

缓存主从同步的解决方案更加灵活:

1.全数据同步。这种可以参考Redis、Mysql等组件的同步思路,切片数据+增量binlog的方式进行同步。例如DCache就是采用类似方式实现; 2.必要数据同步。Cache+DB作为存储,与纯存储的主从同步问题不同,见下图:

缓存数据一致性的问题,本质上是: 主Cache、备Cache、DB三者对外提供的数据一致性。所以有以下特点:

1) master中的脏数据,slave必须要有,否则切换会回档;

2)master中与DB一致的干净数据,slave可以缺少;

这样slave在升级为master的时候,不存在的数据会从DB拉取,所以就以DB为准,这样一来数据一致性可以保证。

不过,slave如果只同步master的脏数据,很可能切换的瞬间,由于缓存命中率低,导致压力瞬间透传给后端DB,造成瞬间服务不可用。所以从可用性的角度讲,建议也把主机的热数据也同步过去。

所以,我们的存储缓存Cube采用了分级同步的方式处理,优先保证数据一致性,其次是数据可用性,同步示意图如下:

优点:

1.分级同步,把危险期降到最低。脏数据同步完,slave就已经可以保证数据一致了,此时就具备最基本的切换能力。脏数据通常占比不高,从我们线上的业务看,峰值占总内存量的1/10,所以脏数据的同步最重要,耗时也较短;

2.热数据可以选择性的同步。热点数据较多的业务,可以设置同步的量大一些,甚至是把主机的所有数据都同步过去,实现主备数据完全一致。这个过程耗时较长,不过即使同步过程中主机挂掉,也不影响数据一致性,所以热点数据的同步速度可以相对慢一些。

缺点:

master和slave之间必须采用row-based binlog方式同步,不可以使用statement-based binlog。

这里再简单介绍下这两种binlog格式的差别:statement-based binlog就是一条操作语句,描述的是执行过程,例如:把某个数据记录中的字段A执行+1操作。row-based binlog描述的是操作执行的结果,例如:字段A执行+1操作之后的结果是什么。

因为slave中的数据量会比master要少,所以statement-based binlog如果在一个不存在的结果上执行,结果肯定是错误的。但是如果是row-based binlog,slave接收到的是执行后的结果,所以就可以直接存储下来。

三 空查询问题

什么是空查询?空查询简单理解就是:到缓存中查询连DB都不存在的数据的请求。

可以不夸张的说:空查询绝对是缓存的天敌。为什么?因为缓存不命中的时候,缓存侧以为是自己没有cache到这个数据,所以把请求透传到后端DB,而DB中也么有这个数据,所以最后缓存中也没被填充上,下次类似的请求过来的时候,同样会走一遍相同流程,很容易导致DB直接过载!

有人可能会问,能不能把不命中的请求也缓存下来,这样下次相同的请求过来,缓存侧就知道这个数据不存在,从而避免再到DB侧拉取数据了呢?答案是不可以!因为空查询的情况可能千差万别。

举个栗子:有一个好友查询系统负责查询玩家A是不是玩家B的好友,如果不是好友就会导致空查询。假设缓存Cache下来A和B不是好友,那么如果前端请求就是各种查询好友关系呢?岂不是缓存系统就会Cache一堆非好友关系??这种消耗肯定是不值得而且得不偿失的。

解决上述问题的一种简单方法是业务方换一种方式拉取数据:好友查询系统一次把所有玩家的好友拉取回去,然后判断是否是好友关系,这样就不会造成空查询。

CKV也遇到过类似的问题,CKV=CMem(纯内存)+TSSD(ssd硬盘),支持一种模式是可以把热点数据缓存在CMem,而把超过多少天没有访问的数据下沉到TSSD。所以CMem中就会因为空查询把请求都透传到TSSD,导致过载的案例:《开空查询对CKV的影响》,最后的解决方案就是在TSSD侧,引入bloom filter的方式,见参考文献3《布隆过滤器loom filter》。

四 总结

本文简单介绍了下分布式缓存的分类、同步和空查询等三个问题。

  1. 从分类看缓存,每个类型都有其自己的特点和优缺点,如何抉择和权衡数据一致性和可用性,是在设计缓存初就需要思考好的问题;
  2. 缓存同步方面,给出了另外一种缓存同步的思路,缓存的主从可以不完全一致,而且数据可以分级同步,效果可能更好;
  3. 空查询问题,这个问题我们在外网也遇到过的头痛问题,缓存也必须要考虑到这方面问题,才不至于因为空查询而失效。

个人经验,和大家一起分享下!

五 参考文献

1 . 关于缓存设计模式的参考文献。

1)《缓存更新的套路》陈皓

2)《Cache Usage Patterns》介绍了几种缓存模式

3)《Caching Guidance》微软云设计模式中缓存的介绍

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 缓存的划分
    • 1 .调用方自己维护缓存(Cache-Aside模式)
      • 2.委托缓存代理侧维护(Cache-Proxy模式)
      • 二 缓存的主从同步
      • 优点:
      • 缺点:
      • 三 空查询问题
      • 四 总结
      • 五 参考文献
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档