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

news2024/11/18 16:38:35

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.coloradmin.cn/o/1399051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS鸿蒙学习基础篇 - 什么是HarmonyOS

概述 HarmonyOS是华为开发的一款面向未来的全场景分布式智慧操作系统,将逐步覆盖18N全场景终端设备; 对消费者而言 HarmonyOS用一个‘统一的软件系统’ 从根本上解决消费者面对大量智能终端体验割裂的问题,为消费者带来同意便利安全的智慧化全…

使用 Python 创造你自己的计算机游戏(游戏编程快速上手)第四版:第十五章到第十八章

十五、反转棋游戏 原文:inventwithpython.com/invent4thed/chapter15.html 译者:飞龙 协议:CC BY-NC-SA 4.0 在本章中,我们将制作反转棋,也称为黑白棋或奥赛罗。这个双人棋盘游戏是在网格上进行的,因此我们…

【Qt5】QString的成员函数trimmed

2024年1月19日,周五下午 QString 的 trimmed 方法是用于移除字符串两端的空白字符(空格、制表符、换行符等)的方法。它返回一个新的字符串,该字符串是原始字符串去除两端空白后的结果。 下面是一个简单的示例: #incl…

【Linux 内核源码分析】堆内存管理

堆 堆是一种动态分配内存的数据结构,用于存储和管理动态分配的对象。它是一块连续的内存空间,用于存储程序运行时动态申请的内存。 堆可以被看作是一个由各个内存块组成的堆栈,其中每个内存块都有一个地址指针,指向下一个内存块…

实体类(VO,DO,DTO)的划分

实体类(VO,DO,DTO)的划分 什么是“实体类” 实体类的主要职责是存储和管理系统内部的信息,它也可以有行为,甚至很复杂的行为,但这些行为必须与它所代表的实体对象密切相关。实体类有两方面内容…

51单片机8*8点阵屏

8*8点阵屏 8*8点阵屏是一种LED显示屏,它由8行和8列的LED灯组成。每个LED灯的开闭状态都可以独立控制,从而可以显示出数字、字母、符号、图形等信息。 8*8点阵屏的原理是通过行列扫描的方式,控制LED灯的亮灭,从而显示出所需的图案或…

使用MySQL建立外键约束时,报错3780的问题分析,和解决办法

今天在用语句给两个表建立外键约束时,报了3780的错误–具体描述如下: 大概意思就是或说,主表和从表的create_use 和 user_id 两个字段这不兼容 经过一顿分析之后发现,是因为这两个表的这两列数据类型不一样 解决办法–修改表中…

毫米波雷达4D点云生成(基于实测数据)

本期文章分享TI毫米波雷达实测4D点云生成的代码,包含距离、速度、水平角度、俯仰角度,可用于日常学习。 处理流程包含:数据读取和解析、MTI、距离估计、速度估计、非相干累积、2D-CFAR、水平角估计、俯仰角估计、点云生成、坐标转换等内容。…

【大数据Hive】hive 行列转换使用详解

目录 一、前言 二、使用场景介绍 2.1 使用场景1 2.2 使用场景2 三、多行转多列 3.1 case when 函数 语法一 语法二 操作演示 3.2 多行转多列操作演示 四、多行转单列 4.1 concat函数 语法 4.2 concat_ws函数 语法 4.3 collect_list函数 语法 4.4 collect_set函…

数据结构之二叉树的性质与存储结构

数据结构之二叉树的性质与存储结构 1、二叉树的性质2、二叉树的存储结构 数据结构是程序设计的重要基础,它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发,分析和研究计算机加工的数据的特性,…

谁说知识库都是英文的 今天就来一个中文版的

1.安装 1.1创建目录 mkdir -p /opt/trilium-cn cd /opt/trilium-cn 1.2.编写docker-compose.yml文件 version: 3 services:trilium-cn:image: nriver/trilium-cnrestart: alwaysports:- "10012:8080"volumes:# 把同文件夹下的 trilium-data 目录映射到容器内- /opt…

5 python快速上手

数据类型(上) 1.整型1.1 定义1.2 独有功能1.3 公共功能1.4 转换1.5 其他1.5.1 长整型1.5.2 地板除 2. 布尔类型2.1 定义2.2 独有功能2.3 公共功能2.4 转换2.5 其他2.5.1 做条件自动转换 3.字符串类型3.1 定义3.2 独有功能(18/48)练…

SpringBoot教程(十五) | SpringBoot集成RabbitMq

SpringBoot教程(十五) | SpringBoot集成RabbitMq RabbitMq是我们在开发过程中经常会使用的一种消息队列。今天我们来研究研究rabbitMq的使用。 rabbitMq的官网: rabbitmq.com/ rabbitMq的安装这里先略过,因为我尝试了几次都失败了,后面等我…

【数据结构】详谈队列的顺序存储及C语言实现

循环队列及其基本操作的C语言实现 前言一、队列的顺序存储1.1 队尾指针与队头指针1.2 基本操作实现的底层逻辑1.2.1 队列的创建与销毁1.2.2 队列的增加与删除1.2.3 队列的判空与判满1.2.4 逻辑的局限性 二、循环队列2.1 循环队列的实现逻辑一2.2 循环队列的实现逻辑二2.3 循环队…

西瓜书读书笔记整理(十二) —— 第十二章 计算学习理论

第十二章 计算学习理论(上) 12.1 基础知识12.1.1 什么是计算学习理论(computational learning theory)12.1.2 什么是独立同分布(independent and identically distributed, 简称 i . i . d . i.i.d. i.i.d.&#xff0…

USRP相关报错解决办法

文章目录 前言一、本地环境二、相关报错信息二、解决办法1、更换电脑操作系统2、升级最新版固件 前言 在进行 USRP 开发时遇到了一些报错,这里做个记录解决问题的方法。 一、本地环境 电脑操作系统:Windows11MATLAB 版本:MATLAB 2021aUSRP …

JVM:Java类加载机制

Java类加载机制的全过程: 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java…

竞赛保研 机器视觉 opencv 深度学习 驾驶人脸疲劳检测系统 -python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 🔥 优质竞赛项目系列&#x…

CSS明显比XPATH更性感!CSS再学一点儿

在selenium应用,CSS比XPATH更性感 To style an element, Front end developers need to locate the element first and then apply styling rules. It looks like this: #logo{ color: white; background: black; }That CSS snippet says, apply color and backgro…

深入探索 Android 中的 Runtime

深入探索 Android 中的 Runtime 一、什么是 Runtime二、Android 中的 Runtime 类型2.1. Dalvik Runtime2.2. ART(Android Runtime) 三、Runtime 的作用和特点3.1. 应用程序执行环境3.2. 跨平台支持3.3. 性能优化3.4. 应用程序优化 四、与应用开发相关的重…