Memcache
memecache是facebook的一个缓存中间件,称为后来系统设计缓存的一个参考。
facebook的建构演变
单体应用
最初用户量不大的时候使用单体应用架构。 数据库使用MySQL, 使用Apache做web服务器,用php生成html。
增加前端服务器
随着用户增加,首先生成html的php服务器出现瓶颈。
于是需要增前端FE服务器,这些服务器共享一个MySQL数据库。
数据库分片
用户进一步增加,单个MySQL数据库也撑不住了。
这时候就需要对MySQL数据库进行分片(分表)。
这是就有了新要求:
-
前端网页服务器需要了解分片规则,同时要和多个数据库进行通信。
-
由于分片(分表),无法依赖单个数据库的事务,需要执行多个数据库之间的
分布式事务
,而分布式事务的执行本身更费时间。
引入缓存
若用户数再进一步增长,对于分片数据库来说,虽然可以进一步增加分片,但:
- 对于热点数据,可能就是同一个数据,无论分片(分表)的粒度多么细,但是热点数据还是位于同个分片内,这时候增加分片并没有太大的帮助。
- 增加分片需要增加数据库,数据库本身执行效率较慢,增加分片代价高。
此时可以选择引入缓存,由于缓存的数据存在RAM中,执行效率远高于db,这种方案就更具性价比。
缓存主要有两种模式:
- look-aside cache: 先去缓存里查数据,缓存未命中的话就需要去数据库查,然后把数据写回数据库。
- look-through cache:缓存就称为数据库的代理了。
Facebook的memcache就属于look-aside cache。
Facebook在使用memcache时主要关注:
- 性能问题:facebook想要引入带来更好的新年,在使用缓存后,能带来远大于数据库本身能承受的数据处理能力,大量的热点读请求都可以只打在缓存上。但是要是缓存出现了某些问题,导致大量的请求在一瞬间达到数据库,数据库会承受不住。
- 缓存一致性问题:memcache和数据库里的数据一致性问题。
性能Performence
分区+复制
Partition:
+ Ram efficient:相同的内存能存更多的数据
- not good for hot keys: 对缓解热点数据帮助不大(有可能热点就是同一个数据,不管怎么分,都在一个分区)
- clients talk to many part: 客户端需要和多个分区通信,可能会建立很多的TCP连接。
Replication:
+ good if hot keys: 对缓解热点数据有帮助
+ few tcp connections:客户端只和一个复制通信,不会有太多的tcp连接
- less total data:相同的内存存的数据少
介于分区和复制各有优缺点,facebook将两者联合起来使用。
facebook在东西海岸有两个不同的reigon。西海岸的reigon作为主数据中心,所有的写请求都达到东海岸,并通过mysql支持的异步复制,在数秒内就可以复制到西海岸的reigon。
在每个reigon内,有一组DB集群。
然后有若干小集群,每个小集群由多个前端服务器,分片的memcache服务器组成。这个小集群不可以太大,防止FE和MC之间有太多的TCP连接,而且可以做到集群内的网络快速通讯。这些小集群共享DB集群。
对于一些不太热门的数据,请求量少,如果在存在每个小集群内,可能会浪费资源。所以facebook引入一组regional pool,即region内所有小集群共享的memcache,将较冷的数据存放在其中,而集群的memcache则主要处理热门数据。
facebook通过上述方式将分区和复制结合起来使用。
冷启动
如果一个小集群刚启动,其上面的memcache中并没有数据,缓存命中率极低。这时候如果将流量引到新启动的这个小集群内,将会有大量的请求打在数据库上。
为了解决这个问题,引入cold start模式,在冷启动模式下,如果新启动的小集群内缓存并未命中,则会向其他集群的memcache请求数据,缓解数据库的压力。
缓存惊群效应 THUNDRING HERD (缓存击穿)
缓存中一个key失效,但是此时多个客户端同时请求该key,由于未命中,所以多个客户端会同时把请求打到db上。
这种行为非常浪费,其实只需要一个客户端去向db请求数据,其他客户端等待该客户端把读取的数据写会缓存即可。
facebook引入LEASE解决:
- 客户端向memcache请求未命中key,memcache会生成一个LEASE(很大的一串数字),并对这个key进行LEASE标记。
- 其他客户端请求该key时,看到LEASE标记,就会被通知等待一段时间后在解决。
- 拿到LEASE的客户端向db请求完数据后,会携带LEASE把数据写回memcache并且释放LEASE标记,只有LEASE正确才可以写回。
- 如果拿到LEASE的客户端超时,memcache也会释放LEASE。
缓存雪崩
如果负责某一段key分片的memcache挂了,这时候这些分片的请求将会打到数据库,给db带来压力。而手动重启或替换一个memcache需要大量时间。
为了应对缓存雪崩,facebook引入gutter server(一小组的备用服务器),这些gutter服务器会临时接管挂掉的memcache,可以一定程度缓解数据库压力。
一致性 consistency
一致性要求
对于facebook来说:
- 无需在意毫秒级别的一致性,但是数据不能过时太久
- 用户刚刚更新了数据,要让他能看到自己的更新
失效策略
再向数据库写入数据后,让memcache中的缓存失效。
需要保证写入数据库以后再删除,在这中间有人去读数据库,然后写入缓存,就会导致缓存里有旧的数据。
为何使用失效策略,而不是更新策略?(写入操作之间的并发)
并发执行时, Set(x, 2)比Set(x,1)先执行了, 导致缓存写入旧的数据。
读和写操作的并发
在这个例子中,C1在数据库中读取了指定key的值,随后C2更新这个key所对应的值
但是,c1将值写回数据库操作慢于c2使得缓存失效的操作,c1向缓存中写入了一个过时的数据。
facebook也是通过LEASE来解决。
- 客户端向缓存的GET请求也会获得一个LEASE,再读完db写回数据时会携带该LEASE
- 客户端更新完数据,除了会使得缓冲中该key失效,也会使得LEASE失效
- memcahe会拒绝失效的LEASE的写入请求。
这样即使c1的set操作晚到,也会因为LEASE失效而无法更新。