6.824 2020 Lecture 16: Scaling Memcache at Facebook

这篇博客探讨了Facebook如何扩展Memcached集群以应对高读取需求,保证最终一致性,以及解决分布式缓存中的竞争条件。主要内容包括网站架构、Memcached集群设计、一致性哈希、mcrouter的作用、冷启动策略以及各种竞争条件的解决方案,如使用lease机制避免数据错漏。
摘要由CSDN通过智能技术生成

参见:https://pdos.csail.mit.edu/6.824/notes/l-memcached.txt

网站架构

FE 前端web服务器(无状态的,所以很简单)

cache 中间memcached

DB 数据库 (西海岸是主数据库,数据只有一份,没有replication。后来增加了东海岸机房,作为从数据库。所有的都走主数据库。)

《Scaling Memcache at Facebook》主要讲的就是中间这个cache层怎么设计。

目标

  1. 为数据库挡下绝大多数的操作。
    网站的读操作远远多于写。
    数据库主要是应付所有的写操作,读操作比写操作多得多得多,如果读都到数据库去读,数据库会撑不住。
    而memcached数据是在内存,所以相同的硬件能比DB应付更多的请求。
  2. 一致性要求。要求最终一致性,数据更新后,用户可以读到几秒钟前的过时的数据,但是最终用户总能读到正确的版本。
    但是有一种情况不可接受,就是数据库的数据更新后,缓存永久性地错过这个更新,非要等到下一次用户再写,缓存才会改变。这不能接受。
  3. 防止数据库被客户端请求挤爆是一个要求。另一个要求是防止路由器被挤爆,出现in-cast的情况。

memcached集群

  1. partition)memcached(mc)服务器之间是不通信的。它的客户端库采用一致性哈希算法,根据key找到对应的mc服务器。虽然你有n台mc服务器组成集群,对于1个key来说,还是只有1台mc服务器提供缓存。
  2. replication)所以,如果有个别key特别热门,比如鹿晗发了张照片,很多人要看。那一个mc集群就不够了。往这个集群里面多加服务器是没用的,因为这改变不了一个key只对应1台服务器。所以是要分成多个mc集群,一组客户端固定访问一组mc服务器,每1组mc服务器都是服务完整的所有key。
  3. 这带来一致性的问题。客户端做完写操作,只是delete自己所属的mc集群上的缓存。而其他mc集群的缓存并不知道这个键值已经改了,文章的解决办法是让数据库去通知可能存有这个键值的mc集群,这个部分叫做McSqueal。

不是所有的键值对都是平等的

有的时候会有矛盾。
有的key/value创建起来很花费数据库资源,但是不常访问,按照LRU算法很容易被清除出缓存。
有的key/value创建起来很容易,经常访问,一直占在缓存里,挤掉上面说的第一类键值对。
这两类存在矛盾,所以最好按照不同性质分开存放在不同的memcache pool里。

churn rate代价
常访问high churn廉价
不常访问low churn昂贵

mcrouter

  1. 一组前端服务器要访问一组mc服务器里的每一个,一次查询可能要查询几百个值,如果同时返回到前端服务器,可能会造成in-cast,解决办法是滑动窗口,限制同时在路上的请求数。
  2. 反过来,数据库的McSqueal要把invalidate(数据已更新,缓存失效)通知到所有的mc集群,它要知道里面所有的机器,这样配置起来就非常复杂。同时向那么多台机器发消息要同时存在的连接数也很多。解决办法是用mcrouter。就是搞成层级制,层层通知下去,类似班长通知组长,组长通知组员。

冷启动

  • 新加入的memcached集群,内容全是空的,如果都从数据库补充,会给数据库造成很大负担。
  • 解决办法:新mc集群的客户端如果在本mc集群get()不到,不是去找db读取,而是先找其他老集群get(),然后set()到新集群。本质上是惰性复制老集群到新集群。
  • 有时候会造成一致性问题。Race 2

Race 2

  • 新mc集群,有2个客户端1、2——客户端1往数据库写了数据,删除了mc上的key-value。客户端2读mc的时候发现key-value不存在,这时数据库存的是新数据,而其他mc集群可能还存有旧数据(因为从数据库更新,到相关mc集群接到invalidation通知,有一定延迟。)如果还按照上述方案,就可能读到旧数据并存到新mc集群(并一直存在那里直到下一次写才会改变)。
  • 解决办法是2秒钟的hold-off,hold-off意思是2秒钟之内不接受新的set(),这样即使客户端2读到旧值,也不会写入到新mc集群中。

竞争条件

首先,读/写的过程是这样:

how do FB apps use mc? Figure 1.
  FB uses mc as a "look-aside" cache
    real data is in the DB
    cached value (if any) should be same as DB
  read:
    v = get(k) (computes hash(k) to choose mc server)
    if v is nil {
      v = fetch from DB
      set(k, v)
    }
  write:
    v = new value
    send k,v to DB
    delete(k)
  application determines relationship of mc to DB
    mc doesn't know anything about DB

论文里举出了3种情况下,缓存会永久性地错过一次更新。都分别给出了解决办法。这些解法其实都挺直观的,但是给人的感觉是修修补补,不是一开始就规划好,是遇到问题才打补丁的解法。

Lease
这里的lease并不是一般《分布式系统》课里面说的lease,本文的lease意思是持有lease的人才允许set()

题外话:一般说的Lease不是这个意思,比如Raft里面的lease是既要允许从leader直接读(不经过raft log),同时又防止脑裂的。因为你(leader)直接读不经过raft log,不能保证网络分区,别人重选,你已经不是现任leader,而新leader已经改了数据。而这个lease在Raft里面的意思是旧leader在lease给的这段时间内保证是leader,别人在这个lease到期前,不投票给新leader。(这个lease也可以用election timeout推算出来1?应该不对,底下评论就有人提出了,实际上是lease期间禁止vote Yes,而不是lease期间不会发生选举。所以你知道tidb的科普文章不是那么可信。)那么在这个lease期间内,可以放心大胆地直接按照自己状态机的数据回复客户端,绝对是最新数据。

Race 1

问题:

第一种竞争是读遇到cache miss/写的竞争。两个客户端C1和C2。
C1读miss了,C1从数据库读值,然后C2往数据库写了新的值,C1将旧值写到cache,那就完蛋了,C1写的这个旧值会一直存在cache里,直到下一次写。

解法:

用lease。C1读miss,mc会给它一个lease,允许它待会儿回来set修改值。但是如果C2写了新值,然后C2 delete() mc里面的key-value,则mc会把之前发的lease设置为失效。C1持着旧的lease回来set时,mc就不理他了。

Race 3

问题:

第三中竞争条件是副本区写操作的问题。写都是写到主数据库,而不写到备份数据库,但是读是读本地的。

那么处在备份数据库所在区的客户端要写,它是直接写到远程的主数据库,此时它本地的备份数据库还存着旧数据。如果处在备份数据库这个区的客户端再读,它会读到过时的数据。

解法:在mc的key-value里面,加一种remote状态。表明此时远端的主服务器已经有更新的数据,我们本地的备份数据库存的是旧的,请到远程去读取。
每次远程写数据库,也要在本地cache的相应key设置remote状态。


  1. TiKV 源码解析系列 - Lease Read ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值