设计社交网络的数据结构

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。设计社交网络的数据结构,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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 None
		if source is dest:
			return [source.key]
		prev_node_keys = self._shortest_path(source, dest)
		if prev_node_keys is None:
			return None
		else:
			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.visited
	while queue:
		node = queue.popleft()
		if node is dest:
			return prev_node_keys
		prev_node = node
		for 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.key
				adj_node.visit_state = State.visited
		return 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_server

    def _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: person

    def 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 = id
		self.name = name
		self.friend_ids = friend_ids

User Graph Service 实现:

class UserGraphService(object):

    def __init__(self, lookup_service):
        self.lookup_service = lookup_service

    def 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 None
        if 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 None
        else:
            # Iterate through the path_ids backwards, starting at dest_key
            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]
            # Reverse the list since we iterated backwards
            return path_ids[::-1]

    def _shortest_path(self, source_key, dest_key, path):
        # Use the id to get the Person
        source = self.person(source_key)
        # Update our bfs queue
        queue = deque()
        queue.append(source)
        # prev_node_keys keeps track of each hop from
        # the source_key to the dest_key
        prev_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 itself
        visited_ids = set()
        visited_ids.add(source.id)
        while queue:
            node = queue.popleft()
            if node.key is dest_key:
                return prev_node_keys
            prev_node = node
            for 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.key
                    visited_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等特定于图形的查询语言(如果没有限制阻止使用图形数据库)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202460.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!