设计一个缓存系统

Posted Daily Problem

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计一个缓存系统相关的知识,希望对你有一定的参考价值。


设计一个 kv 缓存系统,保存最近 web 的查询

1. 准备和计算

面试官一般会提供一些假设和约束,比如用户量,数据量等,我们需要粗略做个计算。

假设这题有以下假设和约束:

  • 用户数 1000w

  • 每月查询量 100 亿

  • 内存有限

  • 要求低延时

  • 服务高可用

我们可以做出如下粗略盘点:

  • 假设每个查询 50 个 bytes,根据查询解析出的 key 算它 20 bytes,value 是一个快照 snippet 算它 200 bytes,那么一次查询共 270 bytes.

  • 每条查询 270 byte ,每月 100 亿查询,如果每次查询都是唯一的话,需要存储 2.7T 数据。

  • 每月 100 亿查询,每月 250万 秒,那么平均每秒 4000 次请求。

1G ≈ 1000M ≈ 1000000K(100万k) ≈ 1000000000b(10亿byte)

1T ≈ 10000亿byte

30 天 = 24 * 30 h = 24 * 30 * 3600 s ≈ 250万 s

2. 抽象设计

3. 核心组件设计

常见的缓存查询做法,通常使用 redis 或者 memcached 来降低延时。从内存顺序读取1 MB 大约需要 250 微秒,从 SSD 读取数据需要 4 倍时间,而从磁盘读取需要 80 倍时间

因为内存有限,这里使用 LRU 缓存,过滤到不常使用的缓存。整个流程大致如下:

  1. client 端发送一个请求到 web server,通常是一个反向代理服务。

  2. web server 将请求转发到 query api server。

  3. query api server 执行以下操作

    1. 解析请求

    2. 检查内存里是否有内容匹配此查询

      • 如果有 (更新缓存到 LRU 头部,返回该值)

      • 如果没有

        • 使用反向索引服务,查询匹配查询的文档

        • 使用文档服务返回查询标题和快照内容

        • 更新内存,并将内容放到 LRU 头部


具体的LRU缓存实现,之前文章提到过:使用一个双向链表,新的内容添加到头部,超过限制,老的会被移除,同时使用一个 hash table 来提高查找速度。

具体代码可以参考 

4. 扩展设计

服务扩展性有很多面可以做,选取一个基线,做假设,再讨论。

比如内存系统为了应对大量请求负载,需要水平扩展机器,有三种主要的方法。

  • 每台机器在集群里保存各自的缓存。(简单,但查找会延时)

  • 每台机器在集群里都保存同一份缓存副本。(简单,但浪费机器内存)

  • 缓存分片,集群里每台机器保存一定hash范围内的缓存。(复杂,理论上最好)

以下一些 topic 几乎每个系统设计都会提到,这里不做扩展,可以自行研究。

  • DNS (域名解析)

  • Load Balancer (负载均衡)

  • Horizontal scaling (水平扩展)

  • Web Server (反向代理)

  • Api Server (应该服务)

  • cache (缓存)

  • Consistency patterns (一致性原则)

  • Availability patterns (可用性原则)

5. 进一步

取决于问题领域和剩余时间。

  • SQL (读写分离,分库分表,分片,sql 调优)

  • NoSql (kv 存储,document 存储,宽表存储,图形数据库)

  • 缓存 (clientcdn,web server,database)

  • 消息队列,微服务,服务发现

  • 其他方面的权衡取舍

6. 其他

持续对系统进行基准测试和监控,发现瓶颈,并解决它,服务扩展一个持续的过程。


以上。


以上是关于设计一个缓存系统的主要内容,如果未能解决你的问题,请参考以下文章

如何缓存片段视图

多级缓存系统设计

千万级并发!如何设计一个多级缓存系统?

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题

在每个用户的Rails中使用片段缓存