设计社交网络的数据结构

1: 确定 Use Case 和 约束

Use Cases

  • User 搜索某人然后看到被搜索人的最短路径
  • Service 有高可用

约束和假设

状态假设
  1. Traffic 不是平均分布的
  • 一些被搜索者是更加受欢迎的,某些被搜索者只会被搜索一次
  • 图数据不适用与单个机器
  • 图的分布是轻量级的
  • 一亿个 User
  • 每个 User 平均有 50 个朋友
  • 十亿个搜索每个月

练习使用更传统的系统 - 不要使用 graph-specific solutions, 比如 GraphQL 或者 图数据库 Neo4j

计算使用量
  1. 50 亿 朋友关系
    • 一亿 users * 50 个 friends / user
  2. 每秒 400 个搜索请求

便利转换索引:

  • 每个月250 万秒
  • 1 个请求 / s = 250 万请求 / 月
  • 40 个请求 / s = 1 亿请求 / 月
  • 400 个请求 / s = 10 亿 请求 / 月

2:创建一个 High Level 设计

Design

3: 设计核心组件

Use Case: User 搜索某人,然后得到被搜索人的最短路径

没有上百万 User 和 数十亿 friend 关系的限制,我们可以解决最短路径问题通过使用 BFS 算法

class Graph(Graph):def shortest_path(self, source, dest):if source is None or dest is None:return Noneif source is dest:return [source.key]prev_node_keys = self._shortest_path(source, dest)if prev_node_keys is None:return Noneelse:path_ids = [dest.key]prev_node_key = prev_node_keys[dest.key]while prev_node_key is not None:path_ids.append(prev_node_key)prev_node_key = prev_node_keys[prev_node_key]return path_ids[::-1]def _shortest_path(self, source, dest):queue = deque()queue.append(source)prev_node_keys = {source.key: None}source.visit_state = State.visitedwhile queue:node = queue.popleft()if node is dest:return prev_node_keysprev_node = nodefor adj_node in node.adj_nodes.values():if adj_node.visit_state == State.unvisted:queue.append(adj_node)prev_node_keys[adj_node.key] = prev_node.keyadj_node.visit_state = State.visitedreturn Node

我们不能把所有的 User 都放在同一个机器里面,我们需要 分片的 User (通过 Person Server)
而且使用 Lookup Service 去访问他们

  1. Client 发送请求到 Web Server
  2. Web Server 转发请求到 Search API server
  3. Search API server 转发请求到 User Graph Service
  4. User Graph Service 做下面的事情:
    • 使用 Lookup Service 去寻找 Person Server, 当当前 User的info 被存储过后
    • 寻找合适的 Person Server去接收当前 User的 friend_ids 的 list
    • 运行 BFS 搜索(使用当前 User 作为 source, 而且当前 User的 friend_ids 作为 ids)
    • 从给定的 id 获取 adjacent_node
      • User Graph Service 将需要再次和 Lookup Service沟通,去决定哪一个 Person Service 存储 adjecent_node 数据(匹配给定的 id)

Lookup Service 实现:

class LookupService(object):def __init__(self):self.lookup = self._init_lookup()  # key: person_id, value: person_serverdef _init_lookup(self):...def lookup_person_server(self, person_id):return self.lookup[person_id]

Person Server 实现:

class PersonServer(object):def __init__(self):self.people = {}  # key: person_id, value: persondef add_person(self, person):...def people(self, ids):results = []for id in ids:if id in self.people:results.append(self.people[id])return results

Person 实现:

class Person(object):def __init__(self, id, name, friend_ids):self.id = idself.name = nameself.friend_ids = friend_ids

User Graph Service 实现:

class UserGraphService(object):def __init__(self, lookup_service):self.lookup_service = lookup_servicedef person(self, person_id):person_server = self.lookup_service.lookup_person_server(person_id)return person_server.people([person_id])def shortest_path(self, source_key, dest_key):if source_key is None or dest_key is None:return Noneif source_key is dest_key:return [source_key]prev_node_keys = self._shortest_path(source_key, dest_key)if prev_node_keys is None:return Noneelse:# Iterate through the path_ids backwards, starting at dest_keypath_ids = [dest_key]prev_node_key = prev_node_keys[dest_key]while prev_node_key is not None:path_ids.append(prev_node_key)prev_node_key = prev_node_keys[prev_node_key]# Reverse the list since we iterated backwardsreturn path_ids[::-1]def _shortest_path(self, source_key, dest_key, path):# Use the id to get the Personsource = self.person(source_key)# Update our bfs queuequeue = deque()queue.append(source)# prev_node_keys keeps track of each hop from# the source_key to the dest_keyprev_node_keys = {source_key: None}# We'll use visited_ids to keep track of which nodes we've# visited, which can be different from a typical bfs where# this can be stored in the node itselfvisited_ids = set()visited_ids.add(source.id)while queue:node = queue.popleft()if node.key is dest_key:return prev_node_keysprev_node = nodefor friend_id in node.friend_ids:if friend_id not in visited_ids:friend_node = self.person(friend_id)queue.append(friend_node)prev_node_keys[friend_id] = prev_node.keyvisited_ids.add(friend_id)return None

我们可以使用 public REST API:

$ curl https://social.com/api/v1/friend_search?person_id=1234

Response:

{"person_id": "100","name": "foo","link": "https://social.com/foo",
},
{"person_id": "53","name": "bar","link": "https://social.com/bar",
},
{"person_id": "1234","name": "baz","link": "https://social.com/baz",
}

4: 扩展设计

Better Design

我们可以有以下优化点:

  • 存储完整或部分BFS遍历,以加速后续在内存缓存中的查找
  • 批处理计算然后存储完整或部分BFS遍历,加速在 NoSQL 数据库中的子序列查询
  • 减少机器跳跃通过批量查找朋友在同一个域名下的 Person Server
    • 通过Location分片的 Person Server去进一步的提高,正如朋友普遍都住的比较近
  • 在同一时刻做两个 BFS 搜索,一个从 source开始,另一个从 destination 开始,然后 merge 这量个 path
  • 人们从 BFS 开始搜索大量的 friend. 然后他们是更喜欢去减少 分页的数字(在当前用户和搜索结果)
  • 在询问用户是否要继续搜索之前,根据时间或跳数设置一个限制,因为在某些情况下,搜索可能需要相当长的时间
  • 使用Neo4j等图形数据库或GraphQL等特定于图形的查询语言(如果没有限制阻止使用图形数据库)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript 学习笔记(WEB APIs Day2)

「写在前面」 本文为 b 站黑马程序员 pink 老师 JavaScript 教程的学习笔记。本着自己学习、分享他人的态度,分享学习笔记,希望能对大家有所帮助。推荐先按顺序阅读往期内容: 1. JavaScript 学习笔记(Day1) 2. JavaSc…

c++QT文件IO

1、QFileDialog文件对话框 与QMessageBox一样,QFileDialog也继承了QDialog类,直接使用静态成员函数弹窗。弹出的结果(选择文件的路径)通过返回值获取。 1)获取一个打开或保存的文件路径 // 获取一个打开或保存的文件路…

插入排序(一)——直接插入排序与希尔排序

目录 一.前言 二.排序的概念及其运用 1.1排序的概念 1.2 常用排序算法 三.常用排序算法的实现 3.1 插入排序 3.1.1 基本思想 3.1.2 直接插入排序 3.1.3 希尔排序(缩小增量排序) 四.全部代码 sort.c sort.h test.c 五.结语 一.前言 本文我们…

微软使其AI驱动的阅读导师免费

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

【android】 android 里写jni

目录 (1) 环境准备 (2) 关联c文件到gradle文件 (3) 生成了 (4) 书写 (5) 使用 (6)业务调用 参考文档 (1) 环境准备 ndk, cmake (2) 关联c文件到gr…

学习笔记应用——创建用户账户并且拥有自己的信息

一、创建用户账户 将建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。我们将创建一个新的应用程序,其中包含与处理用户账户相关的所有功能。 创建user 我们首先使用命令 startapp 来创建一个名为 users 的应用程序&…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型、视觉导航

专属领域论文订阅 关注{晓理紫},每日更新论文,如感兴趣,请转发给有需要的同学,谢谢支持 关注留下邮箱可每日定时收到论文更新服务 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航具身智能,机器人强化学习开放词汇&a…

springcloud OpenFeign服务接口调用

文章目录 代码下载地址OpenFeign简介OpenFeign使用步骤测试 OpenFeign超时控制超时设置,故意设置超时演示出错情况服务提供方8001故意写暂停程序服务消费方80添加超时方法PaymentFeignService服务消费方80添加超时方法OrderFeignController测试YML文件里需要开启Ope…

2.【C语言】(函数指针||sizeof||笔试题)

0x01.函数指针 void test(const char* str) {printf("%s\n", str); }int main() {void (*pf)(const char*) test;//pf是函数指针变量void (*pfarr[10])(const char*);//pfarr是存放函数指针的数组void (*(*p)[10])(const char*) &pfarr;//p是指向函数指针数组…

day3:基于UDP模型的简单文件下载

思维导图 tftp文件下载客户端实现 #include <head.h> #define SER_PORT 69 #define SER_IP "192.168.125.223" int link_file() {int sfdsocket(AF_INET,SOCK_DGRAM,0);if(sfd-1){perror("socket error");return -1;}return sfd; } int filedownloa…

【51单片机系列】单片机与PC进行串行通信

一、单片机与PC机串行通信的设计 工业现场的测控系统中&#xff0c;常使用单片机进行监测点的数据采集&#xff0c;然后单片机通过串口与PC通信&#xff0c;把采集的数据串行传送到PC机上&#xff0c;再在PC机上进行数据处理。 PC机配置的都是RS-232标准串口&#xff0c;为D型…

element-ui 打包流程源码解析(下)

目录 目录结构和使用1&#xff0c;npm 安装1.1&#xff0c;完整引入1.2&#xff0c;按需引入 2&#xff0c;CDN3&#xff0c;国际化 接上文&#xff1a;element-ui 打包流程源码解析&#xff08;上&#xff09; 文章中提到的【上文】都指它 ↑ 目录结构和使用 我们从使用方式来…