跳到主要内容

Facebook如何存储大规模社交图谱(graph)?TAO

· 阅读需 3 分钟

挑战是什么?

在TAO之前,用 cache-aside pattern

在TAO之前

社交图谱是存储在MySQL和缓存在Memcached里的

3个存在的问题:

  1. 在Memcached中更新社交图谱的边列表操作效率太低。不是在列表的后面加一条边,而是要更新整个列表。
  2. 客户端管理缓存的逻辑很复杂
  3. 很难维持==数据库读在写之后这种一致性==

为了解决这些问题,我们有3个目标:

  • 面对大规模数据仍然高效的图(graph)存储
  • 优化读操作(读写比是500:1)
    • 降低读操作的时长
    • 提高读操作的可用性(最终一致性)
  • 及时完成写操作 (先写再读)

数据模型

  • 带 unique ID 的对象 (例如用户,地址,评论)
  • 两个ID之间的关联 (例如被标记,点赞,发表)
  • 以上两个数据模型都有键值数据和时间相关数据

解决方案: TAO

  1. 加快读操作,高效处理大规模的读

    • 专门针对图做缓存
    • 在无状态的服务器层和数据库层中间加一层缓存 (参考 业务拆分)
    • 拆分数据中心 (参考 按数据分割)
  2. 及时完成写操作

    • 透写缓存(write-through cache)
    • 用follower/leader缓存去解决==惊群问题==
    • 异步复制
  3. 提高读操作的可用性

    • 如果读出错,则从其他可读的地方读

TAO 的架构

  • MySQL数据库 → 持久性(durability)
  • Leader缓存 → 协调每个对象的写操作
  • Follower缓存 → 用来读而不是用来写。转移所有的写操作到leader缓存。

Facebook TAO的架构

读操作的容错

Facebook TAO读操作的容错

为什么初创公司必须创新?

· 阅读需 2 分钟

为什么初创公司必须创新?为什么增长技术在特定市场的特定产品或服务上只有效一次?为什么没有一种叫做“商业”的技能?

答案就是==安娜·卡列尼娜原则==。托尔斯泰在《安娜·卡列尼娜》中开篇观察到:“所有幸福的家庭都是相似的;每个不幸的家庭都有自己的不幸。”商业则正好相反。==所有幸福的公司都是不同的:每个公司通过解决一个独特的问题获得垄断。所有失败的公司都是相同的:它们未能逃脱竞争。==

如果一个初创公司不创新,而是从市场领导者那里复制产品或服务,并且该初创公司针对的是相同的市场,那么人们将不会购买它,因为人们可能已经是市场领导者的客户了。为什么人们会为已经满足的需求再花两倍的钱购买同样的东西呢?

为什么初创公司需要创新?

· 阅读需 2 分钟

为什么初创公司需要创新?为什么增长技术只对在某个特定市场的特定产品或者服务有效?为什么没有一种技术叫做“做生意”

答案是==Anna Karenina principle(原则)==。 Tolstoy观察到“快乐的家庭都大差不离,不快乐的家庭各有各的不快乐”,从而得到了这个理论。 然而做生意则是相反的。 ==所有成功的公司是不一样的:每个成功的公司通过解决一个特定的问题来获得一个领域的垄断。所有失败的公司则是相同的:他们没有摆脱市场竞争。==

如果一个初创公司只是照抄现在行业领袖的产品或者服务,没有创新,而且这个初创公司也定位到同样的市场,那么人们不会买他的帐,因为人们可能已经是行业领袖的客户了。如果人们的需求已经被现有的产品实现了,那人们为什么要买同样的东西两次呢?

什么是市场?

· 阅读需 1 分钟

对于高科技,我们可以将市场定义为

  1. 一组实际或潜在的客户
  2. 针对一组特定的产品或服务
  3. 他们有共同的需求或欲望,并且
  4. ==在做出购买决策时相互参考。==

第 4 点是这里的关键 - 相互参考是市场营销成功的关键。如果两个人出于相同的原因购买相同的产品,但没有办法相互参考,他们就不属于同一个市场。他们处于不同的 ==市场细分==。

市场是什么?

· 阅读需 1 分钟

高科技市场的定义是

  1. 现有的和潜在的用户
  2. 对某类产品或者服务
  3. 有需求
  4. 而且==这些人在决定购买的产品的时候会互相参考==

第四点的理解是 - 互相参考是市场成功的关键。如果两个人因为同样的理由买了同一个产品,但是他们没有办法互相参考,他们就不是在同一个市场。他们在不同的==市场段(market segments)==。

技术采纳生命周期中的鸿沟是什么?

· 阅读需 4 分钟

创新是否具有颠覆性?

颠覆性创新与持续创新

  • 它是否 ==改变我们当前的行为模式== 或者 ==修改我们依赖的其他产品和服务==。
  • 持续与非持续之间存在着对行为变化的需求谱系。

高科技行业常常引入颠覆性创新,在此过程中,人们通过遵循正态分布的模式转变为客户。产品的用户增长遵循S曲线。

人们何时会购买高科技产品?

技术采纳生命周期

颠覆性创新的客户在 ==技术采纳生命周期== 的不同阶段被转化。他们是...

  1. 创新者
  2. 早期采用者
  3. 早期大多数(务实者)
  4. 后期大多数(保守者)
  5. 落后者
细分市场他们想要的
创新者新颖、酷炫和实验性的事物
早期采用者获得优势或比其他人更早获得产品
早期大多数经过验证的投资回报,立即访问,低转换成本,提供支持
后期大多数尽可能少地采用,或仅在其他人都采用时才采用
落后者避免采用新事物

高科技营销模型是什么?

这个周期提供了 ==高科技营销模型的指导:开发高科技市场的方法是从左到右推动曲线,逐个关注每个群体==,因为左侧的群体为右侧的群体推广产品,形成势头。

势头至关重要,因为它可以

  1. 节省成本
  2. 加快速度,以便在下一个颠覆或竞争对手出现之前不会错过机会窗口

鸿沟在哪里?

深入观察技术采纳生命周期,我们可以看到 跨越鸿沟

  • 两个裂缝

    1. 创新者与早期采用者之间的有益使用裂缝。例如,世界语、VRML、第二人生、3D打印。要跨越这一点,我们需要一个旗舰应用程序。
    2. 能干的大多数裂缝,早期大多数与后期大多数之间。例如,家庭自动化、扫描和项目管理软件。要跨越这一点,我们需要使其更易于采用。
  • 还有一个鸿沟

    1. 早期采用者到大多数的鸿沟。因为他们的需求不同

      1. 早期采用者购买的是变革代理——他们希望在竞争中获得先机。出现错误是可以接受的。
      2. 务实的早期大多数购买的是生产力提升。他们希望技术 ==增强,而不是推翻,既定的商业运作方式==。
    2. 以上的兼容性导致两个关键点

      1. 早期采用者并不是早期大多数的良好参考
      2. 由于早期大多数担心打乱他们的组织,良好的参考对他们的购买决策至关重要。
    3. 谁在2014年陷入了早期采用者到大多数的鸿沟?例如,全息图、基于笔的平板电脑、燃料电池、二维码(在美国)、大规模开放在线课程、平衡车、摩托罗拉铱星。

什么是科技市场接受周期中的鸿沟?

· 阅读需 5 分钟

创新是破坏性的吗?

破坏性创新(Dsruptive innovation) vs. 连续创新(Continuous innovation)

  • 它是否 ==改变我们现在的行为模式== 或者 ==改变我们依赖的其他产品和服务==。
  • 在连续创新和不连续创新之间,存在着对行为改变的一系列需求。

高科技公司经常引入一些破坏性创新。在这些破坏性创新的过程中,人们成为了这些产品的用户。这种转变通常是以正态分布的形式发生,所以这些产品的用户增长成S曲线。

什么时候人们会购买高科技产品?

科技应用的生命周期

在==科技应用的生命周期==中,人们在不同阶段成为了破坏性创新产品的用户。他们是:

  1. 创造者(Innovators)
  2. 早期接受者(Early adopters)
  3. 大部分早期用户(Early majority 实用派)
  4. 大部分晚期用户(Late majority 保守派)
  5. 落后者

什么是高科技市场模型?

这个周期为==高科技市场模型==提供了指导,==如何发展一个高科技市场,就是让这个周期顺利的从左到右进行,一个一个用户群体,逐个突破。== 借着左边的用户群体的浪潮更容易向右边的用户群体推销产品。

用户浪潮是很重要的,因为它能

  1. 节约成本
  2. 如果你不想在下一次破坏性创新或者竞争者面前错过机会,这会让周期推进得更快

鸿沟在哪里?

观察科技应用的生命周期,我们可以看到 跨过断层

  • 两处裂缝

    1. 在创造者和早期接受者之间存在一个裂缝,即合适的应用场景。例如 Esperanto, VRML, second life, 3D打印。 要跨越这个裂缝,我们需要一个标志性的产品。
    2. 在大部分早期用户和大部分晚期用户之间存在另一个裂缝,即有竞争力的产品。例如 home automation.scanning 和项目管理软件. 要跨越这个裂缝,我们需要让产品更容易被人接受。
  • 和一个断层

    1. 早期接受者到大部分早期用户的断层。因为他们的需求是不一样的:

      1. 早期接受者想要的是一个改变 -- 他们期望能在竞争市场上得到一个飞跃。产品上有些小问题没有关系。
      2. 以实用主义者为主的大部分早期用户想要的是生产效率的提高。他们想要科技去==提高,而不是颠覆他们做事的基本方式==。
    2. 前两个阶段的人和后面两个阶段人的不兼容

      1. 早期接受者的行为对于大部分早期用户来说不是一个很好的参考。
      2. 而且因为大部分早期用户不想破坏他们已有的组织架构。一个好的参考对他们要不要用这个产品的决定是非常重要的。
    3. 谁真的在2014年遇到过这个断层?例如 holograms, pen-based tablets, fuel cells, 二维码(在美国), 大部分在线课程, Segways, Motorola iridium.

Netflix 如何提供观看数据?

· 阅读需 2 分钟

动机

如何在规模上保持用户的观看数据(每天数十亿事件)?

在这里,观看数据指的是...

  1. 观看历史。我看过哪些标题?
  2. 观看进度。我在某个标题中停留在哪里?
  3. 正在观看的内容。我的账户现在还在观看什么?

架构

Netflix 观看数据架构

观看服务有两个层次:

  1. 有状态层 = 活动视图存储在内存中

    • 为什么?为了支持最高的读/写量
    • 如何扩展?
      • 按照 account_id mod N 分区为 N 个有状态节点
        • 一个问题是负载分布不均,因此系统容易出现热点
      • CAP 定理 下选择 CP 而非 AP,并且没有活动状态的副本。
        • 一个失败的节点将影响 1/n 的成员。因此,他们使用过时的数据以优雅地降级。
  2. 无状态层 = 数据持久性 = Cassandra + Memcached

    • 使用 Cassandra 进行非常高的写入量和低延迟。
      • 数据均匀分布。由于使用虚拟节点进行一致性哈希来分区数据,因此没有热点。
    • 使用 Memcached 进行非常高的读取量和低延迟。
      • 如何更新缓存?
        • 在写入 Cassandra 后,将更新的数据写回 Memcached
        • 最终一致性,以处理多个写入者,具有短的缓存条目 TTL 和定期的缓存刷新。
      • 将来,优先考虑 Redis 的追加操作到时间排序列表,而不是 Memcached 中的“读-修改-写”。

如何设计健壮且可预测的 API 以实现幂等性?

· 阅读需 3 分钟

API 如何可能不够健壮且不可预测?

  1. 网络不可靠。
  2. 服务器更可靠,但仍可能出现故障。

如何解决这个问题?三个原则:

  1. 客户端重试以确保一致性。

  2. 使用幂等性和幂等性键进行重试,以允许客户端传递唯一值。

    1. 在 RESTful API 中,PUT 和 DELETE 动词是幂等的。
    2. 然而,POST 可能导致==“双重收费”问题==。因此,我们使用==幂等性键==来识别请求。
      1. 如果故障发生在服务器之前,则会进行重试,服务器将首次看到该请求,并正常处理。
      2. 如果故障发生在服务器中,则 ACID 数据库将通过幂等性键保证事务。
      3. 如果故障发生在服务器回复之后,则客户端重试,服务器简单地回复成功操作的缓存结果。
  3. 使用==指数退避和随机抖动==进行重试。要考虑==雷鸣般的群体问题==,即服务器可能处于降级状态,突发的重试可能会进一步损害系统。

例如,Stripe 的客户端重试计算延迟如下...

def self.sleep_time(retry_count)
# 根据到目前为止的尝试次数应用初始网络重试延迟的指数退避。不要让这个数字超过最大网络重试延迟。
sleep_seconds = [Stripe.initial_network_retry_delay * (2 ** (retry_count - 1)), Stripe.max_network_retry_delay].min

# 通过在 (sleep_seconds / 2) 到 (sleep_seconds) 范围内随机化值来应用一些抖动。
sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))

# 但永远不要少于基础睡眠秒数。
sleep_seconds = [Stripe.initial_network_retry_delay, sleep_seconds].max

sleep_seconds
end

如何使用幂等性设计出高可靠的API?

· 阅读需 2 分钟

为什么API会不可靠?

  1. 网络会出错
  2. 服务器会出错

怎么解决这个问题呢?三个原则

  1. 客户端用“重试”来保证状态的一致性

  2. 重试的请求里要有==幂等的唯一性ID==

    1. 在 RESTful API 设计里面,PUT 和 DELETE 的语义本身是幂等的
    2. 但是 POST 在在线支付领域可能会导致==“重复付两次钱”的问题==,所以我们用“幂等的唯一性ID”来识别某个请求是否被发了多次
      1. 如果错误发生在到达服务器之前,重试过后,服务器第一次见到它,正常处理
      2. 如果错误发生在服务器,基于这个“唯一性ID”,用 ACID 的数据库保证这个事务只发生一次
      3. 如果错误发生在服务器返回结果之后,重试过后,服务器只需要返回缓存过的成功的结果
  3. 重试要负责任,比如遵循==指数退避算法==,因为不希望一大波客户端同时重试。

举个例子,Stripe 的客户端是这样计算重试的等待时间的:

def self.sleep_time(retry_count)
# Apply exponential backoff with initial_network_retry_delay on the
# number of attempts so far as inputs. Do not allow the number to exceed
# max_network_retry_delay.
sleep_seconds = [Stripe.initial_network_retry_delay * (2 ** (retry_count - 1)), Stripe.max_network_retry_delay].min

# Apply some jitter by randomizing the value in the range of (sleep_seconds
# / 2) to (sleep_seconds).
sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))

# But never sleep less than the base sleep seconds.
sleep_seconds = [Stripe.initial_network_retry_delay, sleep_seconds].max

sleep_seconds
end