Update README.md

pull/374/head
lsvih 2017-05-03 13:00:04 +08:00 committed by GitHub
parent 5072dfe96c
commit 9cdf247f4e
1 changed files with 153 additions and 153 deletions

View File

@ -1,101 +1,101 @@
# Design a key-value cache to save the results of the most recent web server queries # 设计一个键-值缓存来存储最近 web 服务查询的结果
*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.* *Note:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。*
## Step 1: Outline use cases and constraints ## Step 1:简述用例与约束条件
> Gather requirements and scope the problem. > 搜集需求与问题的范围。
> Ask questions to clarify use cases and constraints. > 提出问题来明确用例与约束条件。
> Discuss assumptions. > 讨论假设。
Without an interviewer to address clarifying questions, we'll define some use cases and constraints. 我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
### Use cases ### 用例
#### We'll scope the problem to handle only the following use cases #### 我们将把问题限定在仅处理以下用例的范围中
* **User** sends a search request resulting in a cache hit * **用户**发送一个搜索请求,命中缓存
* **User** sends a search request resulting in a cache miss * **用户**发送一个搜索请求,未命中缓存
* **Service** has high availability * **服务**有着高可用性
### Constraints and assumptions ### 限制条件与假设
#### State assumptions #### 状态假设
* Traffic is not evenly distributed * 网络流量不是均匀分布的
* Popular queries should almost always be in the cache * 经常被查询的内容应该一直存于缓存中
* Need to determine how to expire/refresh * 需要确定如何规定缓存过期、缓存刷新规则
* Serving from cache requires fast lookups * 缓存提供的服务需要快速查询
* Low latency between machines * 机器间延迟较低
* Limited memory in cache * 缓存有内存限制
* Need to determine what to keep/remove * 需要决定缓存什么、移除什么
* Need to cache millions of queries * 需要缓存百万的查询
* 10 million users * 1000 万用户
* 10 billion queries per month * 每个月 100 亿次查询
#### Calculate usage #### 计算用量
**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.** **如果你需要进行粗略的用量计算,请向你的面试官说明。**
* Cache stores ordered list of key: query, value: results * 缓存存储的是键值对有序表,键为 `query`(查询),值为 `results`(结果)。
* `query` - 50 bytes * `query` - 50 bytes
* `title` - 20 bytes * `title` - 20 bytes
* `snippet` - 200 bytes * `snippet` - 200 bytes
* Total: 270 bytes * 总计:270 bytes
* 2.7 TB of cache data per month if all 10 billion queries are unique and all are stored * 假如 100 亿次查询都是不同的,且全部需要存储,那么每个月需要 2.7 TB 的缓存空间
* 270 bytes per search * 10 billion searches per month * 单次查询 270 bytes * 每月查询 100 亿次
* Assumptions state limited memory, need to determine how to expire contents * 假设内存大小有限制,需要决定如何制定缓存过期规则
* 4,000 requests per second * 每秒 4,000 次请求
Handy conversion guide: 便利换算指南:
* 2.5 million seconds per month * 每个月有 250 万秒
* 1 request per second = 2.5 million requests per month * 每秒一个请求 = 每个月 250 万次请求
* 40 requests per second = 100 million requests per month * 每秒 40 个请求 = 每个月 1 亿次请求
* 400 requests per second = 1 billion requests per month * 每秒 400 个请求 = 每个月 10 亿次请求
## Step 2: Create a high level design ## Step 2:创建概要设计
> Outline a high level design with all important components. > 列出所有重要组件以规划概要设计。
![Imgur](http://i.imgur.com/KqZ3dSx.png) ![Imgur](http://i.imgur.com/KqZ3dSx.png)
## Step 3: Design core components ## Step 3:设计核心组件
> Dive into details for each core component. > 深入每个核心组件的细节。
### Use case: User sends a request resulting in a cache hit ### 用例:用户发送了一次请求,命中了缓存
Popular queries can be served from a **Memory Cache** such as Redis or Memcached to reduce read latency and to avoid overloading the **Reverse Index Service** and **Document Service**. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.<sup><a href=https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know>1</a></sup> 常用的查询可以由例如 Redis 或者 Memcached 之类的**内存缓存**提供支持,以减少数据读取延迟,并且避免**反向索引服务**以及**文档服务**的过载。从内存读取 1 MB 连续数据大约要花 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。<sup><a href=https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know>1</a></sup>
Since the cache has limited capacity, we'll use a least recently used (LRU) approach to expire older entries. 由于缓存容量有限,我们将使用 LRU近期最少使用算法来控制那些被缓存了很久却没有被使用过的数据的过期。
* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server) * **客户端**向运行[反向代理](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)的 **Web 服务**发送一个请求
* The **Web Server** forwards the request to the **Query API** server * 这个 **Web 服务**将请求转发给**查询 API** 服务
* The **Query API** server does the following: * **查询 API** 服务将会做这些事情:
* Parses the query * 分析查询
* Removes markup * 移除多余的内容
* Breaks up the text into terms * 将文本分割成词组
* Fixes typos * 修正拼写错误
* Normalizes capitalization * 规范化字母的大小写
* Converts the query to use boolean operations * 将查询转换为布尔运算
* Checks the **Memory Cache** for the content matching the query * 检测**内存缓存**是否有匹配查询的内容
* If there's a hit in the **Memory Cache**, the **Memory Cache** does the following: * 如果命中**内存缓存****内存缓存**将会做以下事情:
* Updates the cached entry's position to the front of the LRU list * 将缓存入口的位置指向 LRU 链表的头部
* Returns the cached contents * 返回缓存内容
* Else, the **Query API** does the following: * 否则,**查询 API** 将会做以下事情:
* Uses the **Reverse Index Service** to find documents matching the query * 使用**反向索引服务**来查找匹配查询的文档
* The **Reverse Index Service** ranks the matching results and returns the top ones * **反向索引服务**对匹配到的结果进行排名,然后返回最符合的一个结果
* Uses the **Document Service** to return titles and snippets * 使用**文档服务**返回文章标题与片段
* Updates the **Memory Cache** with the contents, placing the entry at the front of the LRU list * 更新**内存缓存**,存入内容,将**内存缓存**入口位置指向 LRU 链表的头部
#### Cache implementation #### 缓存的实现
The cache can use a doubly-linked list: new items will be added to the head while items to expire will be removed from the tail. We'll use a hash table for fast lookups to each linked list node. 缓存可以使用双向链表实现:新元素将会在头结点加入,过期的元素将会在尾节点被删除。我们使用哈希表以便能够快速查找每个链表节点。
**Clarify with your interviewer how much code you are expected to write**. **向你的面试官告知你准备写多少代码**。
**Query API Server** implementation: 实现**查询 API 服务**
``` ```
class QueryApi(object): class QueryApi(object):
@ -105,8 +105,8 @@ class QueryApi(object):
self.reverse_index_service = reverse_index_service self.reverse_index_service = reverse_index_service
def parse_query(self, query): def parse_query(self, query):
"""Remove markup, break text into terms, deal with typos, """移除多余内容,将文本分割成词组,修复拼写错误,
normalize capitalization, convert to use boolean operations. 规范化字母大小写,转换布尔运算。
""" """
... ...
@ -119,7 +119,7 @@ class QueryApi(object):
return results return results
``` ```
**Node** implementation: 实现**节点**
``` ```
class Node(object): class Node(object):
@ -129,7 +129,7 @@ class Node(object):
self.results = results self.results = results
``` ```
**LinkedList** implementation: 实现**链表**
``` ```
class LinkedList(object): class LinkedList(object):
@ -148,7 +148,7 @@ class LinkedList(object):
... ...
``` ```
**Cache** implementation: 实现**缓存**
``` ```
class Cache(object): class Cache(object):
@ -160,9 +160,9 @@ class Cache(object):
self.linked_list = LinkedList() self.linked_list = LinkedList()
def get(self, query) def get(self, query)
"""Get the stored query result from the cache. """从缓存取得存储的内容
Accessing a node updates its position to the front of the LRU list. 将入口节点位置更新为 LRU 链表的头部。
""" """
node = self.lookup[query] node = self.lookup[query]
if node is None: if node is None:
@ -171,136 +171,136 @@ class Cache(object):
return node.results return node.results
def set(self, results, query): def set(self, results, query):
"""Set the result for the given query key in the cache. """将所给查询键的结果存在缓存中。
When updating an entry, updates its position to the front of the LRU list. 当更新缓存记录的时候,将它的位置指向 LRU 链表的头部。
If the entry is new and the cache is at capacity, removes the oldest entry 如果这个记录是新的记录,并且缓存空间已满,应该在加入新记录前
before the new entry is added. 删除最老的记录。
""" """
node = self.lookup[query] node = self.lookup[query]
if node is not None: if node is not None:
# Key exists in cache, update the value # 键存在于缓存中,更新它对应的值
node.results = results node.results = results
self.linked_list.move_to_front(node) self.linked_list.move_to_front(node)
else: else:
# Key does not exist in cache # 键不存在与缓存中
if self.size == self.MAX_SIZE: if self.size == self.MAX_SIZE:
# Remove the oldest entry from the linked list and lookup # 在链表中查找并删除最老的记录
self.lookup.pop(self.linked_list.tail.query, None) self.lookup.pop(self.linked_list.tail.query, None)
self.linked_list.remove_from_tail() self.linked_list.remove_from_tail()
else: else:
self.size += 1 self.size += 1
# Add the new key and value # 添加新的键值对
new_node = Node(query, results) new_node = Node(query, results)
self.linked_list.append_to_front(new_node) self.linked_list.append_to_front(new_node)
self.lookup[query] = new_node self.lookup[query] = new_node
``` ```
#### When to update the cache #### 何时更新缓存
The cache should be updated when: 缓存将会在以下几种情况更新:
* The page contents change * 页面内容发生变化
* The page is removed or a new page is added * 页面被移除或者加入了新页面
* The page rank changes * 页面的权值发生变动
The most straightforward way to handle these cases is to simply set a max time that a cached entry can stay in the cache before it is updated, usually referred to as time to live (TTL). 解决这些问题的最直接的方法就是为缓存记录设置一个它在更新前能留在缓存中的最长时间这个时间简称为存活时间TTL
Refer to [When to update the cache](https://github.com/donnemartin/system-design-primer#when-to-update-the-cache) for tradeoffs and alternatives. The approach above describes [cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside). 参考 [《何时更新缓存》](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#何时更新缓存)来了解其权衡取舍及替代方案。以上方法在[缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)一章中详细地进行了描述。
## Step 4: Scale the design ## Step 4:规模设计
> Identify and address bottlenecks, given the constraints. > 找到并解决瓶颈,给出限制范围。
![Imgur](http://i.imgur.com/4j99mhe.png) ![Imgur](http://i.imgur.com/4j99mhe.png)
**Important: Do not simply jump right into the final design from the initial design!** **重要提示:不要从最初设计简单地跳到最终设计中!**
State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design. 现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 对替代方案与权衡评估中解决瓶颈问题。4) 重复以上步骤。请阅读[《设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务》](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each? 描述你的初始设计会遇到怎样的瓶颈与如将如何解决它们是很重要的事情。例如加上一个多个 **Web 服务**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们每个的替代方案和需要**权衡**的点又有什么呢?
We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter. 我们将会介绍一些组件来完成设计,并解决架构缩放性问题。内置的负载均衡器将不做讨论以节省篇幅。
*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives: *为了避免重复讨论*,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其总的要点、方案的权衡取舍以及可选的替代方案。
* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system) * [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer) * [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling) * [水平拓展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server) * [反向代理web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer) * [API 服务(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
* [Cache](https://github.com/donnemartin/system-design-primer#cache) * [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns) * [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns) * [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
### Expanding the Memory Cache to many machines ### 将内存缓存扩大到多台机器
To handle the heavy request load and the large amount of memory needed, we'll scale horizontally. We have three main options on how to store the data on our **Memory Cache** cluster: 为了解决庞大的请求负载以及巨大的内存需求,我们将要对架构进行水平拓展。如何在我们的**内存缓存**集群中存储数据呢?我们有以下三个主要可选方案:
* **Each machine in the cache cluster has its own cache** - Simple, although it will likely result in a low cache hit rate. * **缓存集群中的每一台机器都有自己的缓存** - 简单,但是它会降低缓存命中率。
* **Each machine in the cache cluster has a copy of the cache** - Simple, although it is an inefficient use of memory. * **缓存集群中的每一台机器都有缓存的拷贝** - 简单,但是它的内存使用效率太低了。
* **The cache is [sharded](https://github.com/donnemartin/system-design-primer#sharding) across all machines in the cache cluster** - More complex, although it is likely the best option. We could use hashing to determine which machine could have the cached results of a query using `machine = hash(query)`. We'll likely want to use [consistent hashing](https://github.com/donnemartin/system-design-primer#under-development). * **对缓存进行[分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片),分别部署在缓存集群中的所有机器中** - 更加复杂,但是它是最佳的选择。我们可以使用哈希,用查询语句 `machine = hash(query)` 来确定哪台机器有需要缓存。当然我们也可以使用[一致性哈希](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#正在完善中)。
## Additional talking points ## 其它要点
> Additional topics to dive into, depending on the problem scope and time remaining. > 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
### SQL scaling patterns ### SQL 缩放模式
* [Read replicas](https://github.com/donnemartin/system-design-primer#master-slave) * [读取复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
* [Federation](https://github.com/donnemartin/system-design-primer#federation) * [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
* [Sharding](https://github.com/donnemartin/system-design-primer#sharding) * [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization) * [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning) * [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
#### NoSQL #### NoSQL
* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store) * [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
* [Document store](https://github.com/donnemartin/system-design-primer#document-store) * [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store) * [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database) * [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql) * [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
### Caching ### 缓存
* Where to cache * 在哪缓存
* [Client caching](https://github.com/donnemartin/system-design-primer#client-caching) * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
* [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching) * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
* [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching) * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
* [Database caching](https://github.com/donnemartin/system-design-primer#database-caching) * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
* [Application caching](https://github.com/donnemartin/system-design-primer#application-caching) * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
* What to cache * 什么需要缓存
* [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level) * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
* [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level) * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
* When to update the cache * 何时更新缓存
* [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside) * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
* [Write-through](https://github.com/donnemartin/system-design-primer#write-through) * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
* [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back) * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
* [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead) * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
### Asynchronism and microservices ### 异步与微服务
* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues) * [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues) * [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure) * [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
* [Microservices](https://github.com/donnemartin/system-design-primer#microservices) * [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
### Communications ### 交流
* Discuss tradeoffs: * 可权衡选择的方案:
* External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest) * 与客户端的外部交流 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
* Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc) * 内部交流 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery) * [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
### Security ### 安全性
Refer to the [security section](https://github.com/donnemartin/system-design-primer#security). 请参阅[《安全》](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)一章。
### Latency numbers ### 延迟数值
See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know). 请参阅[《每个程序员都应该知道的延迟数》](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
### Ongoing ### 持续探讨
* Continue benchmarking and monitoring your system to address bottlenecks as they come up * 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
* Scaling is an iterative process * 架构拓展是一个迭代的过程。