设计一个Key-Value缓存去存储最近的Web Server查询的结果

1: 定义Use Case和约束

Use Cases

我们可以定义如下 Scope:

  • User 发送一个 search request, 缓存命中成功返回Data
  • User 发送一个 search request, 缓存未命中,未成功返回Data
  • Service 有高可用

约束和假设

状态假设
  1. Traffic 分布不是均匀的
    • 热度高的查询总是被缓存
    • 需要去确定怎样去 expire/refresh
  2. Cache 服务的查询要快
  3. 在机器之间有低延迟
  4. 在内存中有受限的内存
  • 需要决定神魔时候保留/移除缓存
  • 需要缓存数百万Query
  1. 1千万 user
  2. 100 亿 query/ 月
计算使用量
  1. Cache 存储排序后的 key 的 list: query, value: list
  • query - 50 bytes
  • title - 20 bytes
  • snippet - 200 bytes
  • Total: 270 bytes
  1. 2.7 TB 缓存数据 / 月,如果所有 100 亿query 都是独特的,并且都被存储
  • 270 bytes / 每个搜索 * 100 亿 搜索 / 每月
  • 假设状态受限的内存,需要去决定怎样去 exopire 内容
  1. 4000 请求 / s

简易转换指南:

  1. 250 万 s/ 每个月
  2. 1 request / s = 250 万请求 / 月
  3. 40 request / s = 1 亿 请求 / 月
  4. 400 request / s = 10 亿 请求 / 月

2: 创建一个High Level Design

Design

3: 设计核心组件

Use Case: User 发送一个请求,然后缓存命中

由于Cache的容量是固定的,我们将使用 a least recently used(LRU) 方法去过期的老 entiies.

  1. Client 发送一个请求到 Web Server
  2. Web Server 转发请求到 Query API Server
  3. Query API 会做下面的事情
  • 解析 query
    • 移除 markup
    • 分解 text 进 terms
    • Fix typos
    • 格式化首字母
    • 转换 query 去 使用 bool 操作
  • 检查缓存中和 query 匹配的 content
    • 如果有缓存击中,缓存会做下面的事情
      • 更新缓存entry的位置到LRU List的前面
      • 返回缓存内容
    • 否则,Query API 做下面的事情:
      • 使用 Reverse Index Service 去寻找到匹配 query 的 document
        • Reverse Index Service 排序搜索结果然后返回Top one
      • 使用 Document Service 去返回 title 和 snippets
      • 伴随着 content去更新 Memory Cache,放这个 entry 进 LRU list的前面
Cache 实现

cache 使用双向链表:新的 items 会被添加到头节点,当items 过期时会被为尾节点移除,我们可以使用Hash 表来快速查找每一个 linked list node.

Query API Server 实现:

class QueryApi(object):
	def __init__(self, memory_cache, reverse_index_service):
		self.memory_cache = memory_cache
		self.reverse_index_service = reverse_index_service

	def parse_query(self, query):
		"""Remove markup, break text into terms, deal with typos,
        normalize capitalization, convert to use boolean operations.
      """
	def process_query(self, query):
		query = self.parse_query(query)
		results = self.memory_cache.get(query)
		if results is None:
			results = self.reverse_index_service.process_search(query)
			self.memory_cache.set(query, results)
		return results

Node 实现:

class Node(object):
	def __init__(self, query, results):
		self.query = query
		self.results = results

LinkedList 实现:

class LinkedList(object):
	
	def __init__(self):
		self.head = None
		self.tail = None

	def move_to_front(self, node):
		...
	
	def append_to_front(self, node):
		...
	
	def remove_from_tail(self):
		...

Cache 实现:

class Cache(object):
	def __init__(self, MAX_SIZE):
		self.MAX_SIZE = MAX_SIZE
		self.size = 0
		self.lookup = {}
		self.linked_list = LinkedList()

	def get(self, query)
		""" Get the stored query result from the cache
			Accessing a node update its position to the front of the LRU list.
			"""
			node = self.lookup[query]
			if node is None:
				return None
			self.linked_list.move_to_front(node)
			return node.results

	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.
			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]
			if node is not None:
				node.results = results
				self.linked_list.move_to_front(node)
			else:
				if self.size == self.MAX_SIZE:
					self.loopup.pop(self.linked_list.tail.query, None)
					self.linked_list.remove_from_tail()
				else:
					self.size += 1
				new_node = Node(query, results)
				self.linked_list.append_to_front(new_node)
				self.lookup[query] = new_node

会在如下时间Update Cache:

  • Page 的 content 改变
  • Page 被移除 或者 新page 被添加
  • Page 排名改变

处理这些情况最简单的方法是简单地设置缓存条目在更新之前可以保留在缓存中的最大时间,通常称为生存时间(TTL)。

4:扩展设计

扩展缓存到多台机器

为了处理重负载请求和大量需要的内存,我们需要水品扩展,我们有三个选择关于怎样存储数据进 Memory Cache cluster:

  • 每一个在缓存集群中的机器有自己的 Cache - 简单,尽管它会有一个低缓存命中率的结果
  • 每一个在缓存集群中的机器有自己的 Cache 复制 - 简单,尽管hi有无效的内存使用
  • 缓存集群重的缓存会被分散在所有的机器上 - 复杂,尽管这可能是最好的选项,我们可以使用 Hash去确定哪一个机器会得到针对单个查询的结果,通过使用 machine = hash(query. 我们将想要去使用 持续性 hash.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/336452.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

接口测试遇到500报错?别慌,你的头部可能有点问题

🔥 交流讨论:欢迎加入我们一起学习! 🔥 资源分享:耗时200小时精选的「软件测试」资料包 🔥 教程推荐:火遍全网的《软件测试》教程 📢欢迎点赞 👍 收藏 ⭐留言 &#x1…

class_13:静态成员static关键字

#include <iostream>using namespace std;class Myclass{ public:int datas;static int staticValue; //静态成员变量在类外进行初始化void printInfo(){cout<<datas<<endl;}static int getStaticDatas()//静态成员函数不能直接访问非静态变量和非静态函数&a…

Python sleep函数用法:线程睡眠

如果需要让当前正在执行的线程暂停一段时间&#xff0c;并进入阻塞状态&#xff0c;则可以通过调用 time 模块的 sleep(secs) 函数来实现。该函数可指定一个 secs 参数&#xff0c;用于指定线程阻塞多少秒。 当前线程调用 sleep() 函数进入阻塞状态后&#xff0c;在其睡眠时间…

GPT应用_AutoGPT

项目地址&#xff1a;https://github.com/Significant-Gravitas/AutoGPT 1 功能 1.1 整体功能&#xff0c;想解决什么问题 单独使用 ChatGPT 时&#xff0c;只提供基本的聊天&#xff0c;无法实现复杂多步的功能&#xff0c;以及与其它应用交互&#xff0c;如果想提供某种功…

linux(七):I2C(touch screen)

本文主要探讨210触摸屏驱动相关知识。 I2C子系统 i2c子系统组成部分:I2C核心,I2C总线驱动,I2C设备驱动 I2C核心&#xff1a;I2C总线驱动和设备驱动注册注销方法 I2C总线驱动&#xff1a;I2C适配器(I2C控制器)控制,用于I2C读写时序(I2C_adapter、i2c_a…

2023:既是结束也是开始

2023年注定是不平凡的一年&#xff0c;这一年真的经历了很多事&#xff0c;包括学习、生活、工作等等&#xff0c;上半年忙着毕业以及一些其他的事情&#xff0c;很多挖的坑都没来得及填&#xff0c;下半年研一开学以后终于有了足够的时间学习&#xff0c;接下来就用这篇文章来…

MySQL执行计划全面解析

执行计划 如果不知道执行计划&#xff0c;那就不可能进行SQL优化&#xff0c;那么执行计划是什么呢&#xff1f; 使用explain关键字可以模拟优化器执行SQL查询语句&#xff0c;从而知道MySQL是如何处理你的SQL的&#xff0c;进而分析性能瓶颈 用起来其实很简单&#xff0c;使用…

Swin版VMamba来了!精度再度提升,VMamba-S达成83.5%,超越Swin-S,已开源!

本文首发&#xff1a;AIWalker 就在昨日&#xff0c;华科王兴刚团队公开了Mamba在ViT的入局Vim&#xff0c;取得了更高精度、更快速度、更低显存占用。相关信息可参考&#xff1a; 入局CV&#xff0c;Mamba再显神威&#xff01;华科王兴刚团队首次将Mamba引入ViT&#xff0c;更…

Java 内存模型深度解析

优质博文&#xff1a;IT-BLOG-CN 一、并发编程模型的两个关键问题 【1】并发中常见的两个问题&#xff1a;线程之间如何通信及线程之间如何同步。通信是指线程之间以何种机制来交换信息。在命令式编程中&#xff0c;线程之间的通信机制有两种&#xff1a;内存共享和消息传递&…

Redis 存在线程安全问题吗?为什么?

一个工作了 5 年的粉丝私信我。 他说自己准备了半年时间&#xff0c;想如蚂蚁金服&#xff0c;结果第一面就挂了&#xff0c;非常难过。 问题是&#xff1a; “Redis 存在线程安全问题吗&#xff1f;” 一、问题解析 关于这个问题&#xff0c;我从两个方面来回答。 第一个&a…

ChatGPT 到 Word:使用 Writage 进行复制粘贴魔法

ChatGPT 到 Word&#xff1a;使用 Writage 进行复制粘贴魔法 写在前面Writage的使用 写在前面 随着ChatGPT的日益普及&#xff0c;越来越多的人每天依赖它来完成各种任务。无论是寻找信息、语言翻译、解决数学问题&#xff0c;还是精炼复杂的概念和文本&#xff0c;ChatGPT 都…

AWS CI/CD之二:配置CodeDeploy

问题 前面一篇文章介绍了CodeBuild中构建一个Java的Maven项目。在这个基础上面&#xff0c;我们继续AWS CI/CD工作流构建之路。 1.配置CodePipeline简配版 这里主要是利用CodePipeline配置之前的CodeBuild项目&#xff0c;以便生产出需要部署的jar文件和CodeDeploy需要用到相…

【rust/bevy】使用points构造ConvexMesh

目录 说在前面问题提出Rapier具体实现参考 说在前面 操作系统&#xff1a;win11rust版本&#xff1a;rustc 1.77.0-nightlybevy版本&#xff1a;0.12 问题提出 在three.js中&#xff0c;可以通过使用ConvexGeometry从给定的三维点集合生成凸包(Convex Hull) import { ConvexGeo…

【c++】——栈or队列or优先级队列

目录 &#x1f393;容器适配器 &#x1f393;Stack栈 &#x1f6a9;Stack的介绍 &#x1f6a9;Stack的基本使用 &#x1f6a9;Stack底层实现 &#x1f393;queue队列 &#x1f6a9;queue的介绍 &#x1f6a9;queue的基本使用 &#x1f6a9;queue的底层实现 &#x1…

爬虫之牛刀小试(八):爬取微博评论

今天爬取的是微博评论。 可以发现其特点是下一页评论的max_id在上一页中。 于是代码如下&#xff1a; import requests import json import re import time headers {User-Agent: ,"Cookie": "","Referer": "https://m.weibo.cn/detail/4…

Kafka-消费者-KafkaConsumer分析-PartitionAssignor

Leader消费者在收到JoinGroupResponse后&#xff0c;会按照其中指定的分区分配策略进行分区分配&#xff0c;每个分区分配策略就是一个PartitionAssignor接口的实现。图是PartitionAssignor的继承结构及其中的组件。 PartitionAssignor接口中定义了Assignment和Subscription两个…

网络安全全栈培训笔记(54-服务攻防-数据库安全RedisHadoopMysqla未授权访问RCE)

第54天 服务攻防-数据库安全&Redis&Hadoop&Mysqla&未授权访问&RCE 知识点&#xff1a; 1、服务攻防数据库类型安全 2、Redis&Hadoop&Mysql安全 3、Mysql-CVE-2012-2122漏洞 4、Hadoop-配置不当未授权三重奏&RCE漏洞 3、Redis-配置不当未授权…

Laya3.0 相机使用

摄像机&#xff0c;是3D场景里边最经常使用的对象了。 官方文档&#xff1a;点击这里学习 1.投影 Projection 透视&#xff1a; 模拟人眼的视觉效果&#xff0c;近大远小。模拟物理世界的规律&#xff0c;将眼睛或相机抽象成一个点&#xff0c;此时视锥体内的物体投影到视平…

51单片机独立按键

独立按键介绍 在嵌入式系统中&#xff0c;独立按键通常指的是单独的按键开关或按钮&#xff0c;它们通常用于接收用户输入或执行特定的功能。在51单片机&#xff08;指的是Intel 8051或其兼容芯片&#xff09;中&#xff0c;独立按键可以通过简单的硬件连接和软件编程来实现各种…

Grafana(三)Grafana 免密登录-隐藏导航栏-主题变换

一. 免密登录 Grafana 的常用方式&#xff1a; 将配置好的Grafana图嵌入到系统页面中 为了实现可免登录访问&#xff0c;可以通过如下方式进行设置&#xff1a; 1. 修改Grafana配置文件 在Grafana的配置文件 /etc/grafana/grafana.ini 中&#xff0c;找到 [auth.anonymous] 配…
最新文章