跳到主要内容

49 篇博文 含有标签「system-design」

查看所有标签

如何设计区块链服务端的架构?

· 阅读需 7 分钟

需求分析

  • 分布式的区块链记账和智能合约系统
  • 节点之间相互不大信任,但是又需要激励他们互相合作
    • 交易不可逆
    • 不依赖可信的第三方
    • 保护隐私,透露最少信息
    • 不依赖中心化的权威证明一笔钱不能花两次
  • 假设性能不是问题,暂不考虑如何优化性能

构架设计

具体模块和他们之间的交互

基础层(P2P 网络、加密算法、存储)

P2P 网络

分布式系统有两种实现方式:

  • 中心化的 lead / follower 的分布式,比如 Hadoop, Zookeeper 这种系统,结构比较简单,但是对 lead 要求高
  • 去中心化的对等 (P2P) 网络分布式,比如 Chord, CAN, Pastry, Tapestry 算法组织起来的网络,结构比较复杂,但是更加平等

因为前提条件是节点之间不大信任,所以选择 P2P 的形式。具体到如何组织 P2P 的网络呢?一个典型的去中心化的节点和网络是这样保持连接的:

  1. 基于 IP 协议,节点上线占用某个地址 hostname/port,利用初始化的节点列表广播自己的地址,利用这些初始的 hop,试图向全网 flood 自己的信息
  2. 接到广播的初始节点一方面存下这个 neighbor,一方面帮助他 flooding,不相邻的节点收到后 NAT 穿墙加 neigbhor
  3. 节点之间 anti-entropy 随机互相 heartbeat 发出最新的带着类似 vector clock 的信息,保证能够持续更新对方那里自己的最新信息

我们可以利用既有的库,比如 libp2p,来实现网络模块。网络协议的选择见Crack the System Design Interview: Communication.

加密算法

在互相不大信任的分布式系统中,一笔转账如何在不泄漏自己秘密信息的同时证明这个转账是自己发起的呢?非对称加密:一对公钥和私钥对应一个”所有权”。Bitcoin 选择 secp256k1 参数的 ECDSA 椭圆曲线加密算法,为了兼容,其他链也基本选择同样的算法

为什么不直接把公钥作为转账的地址呢?隐私问题,交易的过程应该尽可能少地泄漏信息,用公钥的哈希作为“地址”可以避免接收方泄露公钥。甚至,人们应该避免反复使用同一个地址

具体到账本的账户,有两种实现方式 UTXO vs. Account/Balance

  • UTXO (unspent transaction output) ,例如 Bitcoin,类似于复式记账的 credit 和 debit,每个 transaction 有都有 input 和 output,但是除了 coinbase 每个 input 的前面都连着上一个的 output。尽管没有账户的概念,但是取一个地址对应的所有的未花完的 output,就是这个地址的余额。
    • 优点
      • 精准:类似于复式记账的结构,让流水账非常精准地记录下所有的资产流动。
      • 保护隐私和抗量子攻击:如果用户经常换地址的话。
      • 无状态:为提高并发留下了可能。
      • 避免重放攻击:因为重放会找不到 input 对应的 UTXO
    • 缺点
      • 记录了所有的交易,复杂,消耗存储空间。
      • 遍历 UTXO 花时间。
  • Account/Balance,例如 Ethereum,有三个主要的 map:account map, transaction map, transaction receipts map. 具体在实现上,为了缩减空间、防篡改,使用 merkle patricia trie (MPT)
    • 优点
      • 省空间:不像是 UTXO 那样,一个 transaction 把多个 UTXO 联系起来
      • 简单:把复杂性让给了 script
    • 缺点
      • 需要用 nonce 解决重放问题,因为 transaction 之间没有依赖性

值得一提的是 “区块 + 链” 的数据结构本质上来讲,就是 append-only 的 Merkle tree,也被称为 hash tree.

存储

因为本身 UTXO 或者 MPT 这种结构就充当了索引,加上分布式的时候,为了让每个节点运维更加简单,一般做 data persistence 的时候倾向于选择 in-process database 随着节点的程序能够直接跑,比如 LevelDB, RocksDB。

因为这种索引并不是通用的,所以你并不能像是 SQL 数据库那样查询,这也为数据分析提高了门槛,优化时需要专门做一个 indexer 服务,比如 etherscan。

协议层

现在我们有了可以操作的基础层后,在这层之上,我们需要一个比较通用的逻辑操作的协议层。根据这个区块链的使用需求,可以像是微内核构架那样,插拔具体的逻辑处理模块。

比如最常见的记账:在最新的 block 高度收到了一些 transaction,组织起来建立如上一层构架所说的数据结构。

为每一个业务逻辑写一个原生模块然后更新所有节点的代码不大现实,用虚拟化的方法解耦这一层?答案是能够执行 smart contract 代码的虚拟机。在互不可信的环境中,不能让客户白执行代码,所以这个虚拟机最独特的功能可能是计费。

基于 contract 的 token 比如 ERC20 和 native token 的区别,导致在不同的 token 捣腾的时候很麻烦,于是就出现了 Wrapped Ether 这种 token.

共识层

协议层算出来执行的结果之后,如何跟其他的节点达成一致呢?有如下一些常见的激励大家合作的机制:

  • proof of work (POW): 用 hash 的碰撞挖 token,耗电量大不环保
  • proof of stake (POS): 用质押的 token 挖 token
  • delegated proof-of-stake (DPOS): 选人民代表用质押的 token 挖 token

在激励机制的基础上,节点中谁的链最长听谁的,两群人互不待见就分叉。

同时,有这样一些一致性协议让大家达成共识(也就是大家要么一起都干,要么一起都不干)

  • 2PC:大家都依赖某一个 coordinator:coordinator 问大家:要不要干?只要有人回复不干,那么 coordinator 跟所有人都说“不干”;否则都说干。 这样的依赖会导致,如果 coordinator 在第二个阶段的中间挂了,有些节点会不知道怎么办 block 在那里,需要人工干预重启 coordinator。
  • 3PC:为了解决上述问题,加一个保证大家在干之前都知道所有人要干还是不干的阶段,出了错就重新选 coordinator
  • Paxos:上述的 2PC 和 3PC 都依赖某一个 coordinator,如何干掉这个 coordinator 呢?用“大多数(2f + 1 里至少 f+1)”来取代,在两步中,只要大多数取得一致,最后就能取得一致。
  • PBFT (deterministic 3-step protocol): 上述的做法容错率还是不够高,于是有了 PBFT。用来保证大多数(2 / 3)节点要么都同意,要么都不同意的算法,具体做法是三轮投票,每轮有至少大多数(2 / 3)节点同意,最后一轮才 commit block 。

在具体的应用中,关系数据库大多用 2PC 或者 3PC ;Paxos 的变种有 Zookeeper 的实现,Google Chubby 分布式锁的实现,Spanner 的实现;区块链中,Bitcoin, Ethereum 是 POW,新的 Ethereum 是 POS,IoTeX 和 EOS 是 DPOS。

API 层

Public API choices

设计以人为本的国际化(i18n) 工程方案

· 阅读需 10 分钟

需求分析

如果问硅谷的公司跟中国最大的不同是什么,我想答案很可能正如吴军所言,硅谷公司的产品大多面向全球市场。陈志武说的好,创造财富能力有三个衡量维度:深度,即生产力,同样的时间提供更好产品或服务的能力;长度,即利用金融杠杆,跨越时间和空间交换价值的能力;广度,即市场大小,开创跨越地域的市场或者新行业的能力。而国际化,也就是产品和服务在语言和文化上的本地化,正是跨国公司征战全球市场的战略要地。

Internationalization 因为字母 i 后面跟了 18 个字母然后以 n 结尾,所以被称为 18n,我们这次设计的 i18n 工程方案主要是解决网站和移动 App 开发过程中的如下问题:

  1. 语言
  2. 时间与时区
  3. 数字与货币

构架设计

语言

逻辑和细节

语言的本质是把消息交付给受众的媒介,不同的语言就是不同的媒介,不同的媒介面向不同的受众。比如,我们要对用户显示文字:“你好,小丽!”,显示的过程就是查一下语言表,根据用户的语言,和当前需要的插值,比如姓名,显示相应的消息:

Message CodesLocalesTranslations
home.helloenHello, ${username}!
home.hellozh-CN你好, ${username}!
home.helloIW!${username}, שלום

不同语言在细节上略有不同,比如一个物品的单数和复数的形式;比如第三人称,在称呼上男性和女性的区别。

这些都是简单的查表无法应对的问题,需要更复杂的逻辑处理。在代码中你可以无脑地使用条件语句去处理这些特例。此外有一些国际化的框架会发明 DSL (domain specific language) 来专门应对这种情况。以 The project fluent 为例:

还有一个新手容易忽略的问题是行文的方向。中文和英文等常用语言是从左至右的,但是还有一些语言,是从右往左的,比如希伯来文和阿拉伯文。

行文方向的不同不仅仅会影响到文字本身,还会影响到输入的方式。中国人如果从右至左输入会觉得非常的奇怪;而我的一位犹太同事就觉得英文和犹太文混着输入轻而易举。

还有一种情况就是布局。整个 UI 的布局、视觉元素比如箭头的方向。都可能会根据语言的方向的不同而发生变化。你的 HTML 需要设置好相应的 dir 属性

如何确定用户的地域?

你可能会问,我们如何知道用户当前的语言设置呢?如果是浏览器的话。在用户请求网页的时候,会有一个 header Accept-Language 标注接受的语言。这些设置来自于用户的系统语言,以及浏览器的设置。移动 App 情况下,通常都会有获取 locale 变量或者常量的 API。还有一种方式是根据用户的IP 或者 GPS 信息知道用户的位置,然后显示相应的语言。如果是跨国公司,用户在注册的时候,通常会标注出用户注册时候的语言习惯、地理区域。

如果用户想要改换语言,网站的做法各有千秋,移动 App 会有相对固定的 API。网页有这样几种方法:

  1. 设置 locale cookie
  2. 使用不同的子域名
  3. 使用专有域名。 Pinterest 有一篇文专门讲他们如何用本地化的域名。 研究表明使用本地域名后缀的点击率会更高。
  4. 使用不同的路径
  5. 使用 query params。这个做法虽然能用,但是对 SEO 不友好。

新手在做网站的时候容易忘记在 HTML 上标注 lang 标签。

翻译管理系统

当你注意到如上种种细节。小心翼翼的实现了文字语言的显示之后。你会发现,翻译库的建立和管理,也是一个麻烦的过程。

通常开发者并不会有多语言的功底。这时候就需要引入外部的翻译官或者是别人已经建立好的翻译库。而这里的难点在于,翻译官往往并不是技术人员。如果让他们直接改代码、或者直接跟开发人员沟通,会极大的增加翻译的成成本。所以在硅谷的公司,这种提供给翻译官使用的翻译管理系统 (translation management system),往往是有一个团队专门来做,或者直接采购现有的方案,比如说,闭源收费的 lokalise.co ,或者是开源的 Mozilla Pontoon。翻译管理系统可以统一管理翻译库、项目、审核、任务分配。

这样一来,开发的流程就会变成,首先设计师根据不同的语言和文化习惯,在设计的时候标志出需要注意的地方,比如这个按钮虽然在英文里很短,但是在俄文里面会非常长,要注意不要溢出。然后,开发者开发团队根据设计的需求实现具体的代码逻辑,并在翻译管理系统中提供消息码、上下文的背景、以及一个开发者熟悉的语言写成的例子。再然后,翻译官团队在管理系统中填上各种语言的翻译。最后,开发团队把翻译库拉回代码库中,发布到产品中。

其中,上下文的背景是容易被忽视而且不容易做好的地方。这个需要翻译的消息在UI界面的什么地方?用作什么用途,如果消息过短的话,还应该进一步的解释这个消息是什么意思。那么翻译官有了这个背景知识之后,就能够更加精准地加上其他语言的翻译。如果翻译官对想要表达的信息。无法透彻的理解。他们还需要拥有一个提供反馈的渠道,能够找到产品的设计和开发者询问问题。

这么多的语言和文字,通常都不是由一个翻译官来解决的,这通常需要很多个国家语言身份的人一起来为这个翻译库添砖加瓦。整个过程耗时耗力,所以翻译官通常是有专门的团队来负责的,比如外包给 smartling。

现在我们已经有了代码逻辑和翻译库。接下来的问题是:如何把翻译库的内容搬到产品中?

具体可以有很多不同的实现方式,最直接的就是,静态的做法,每次更新的时候。交一个 diff,然后在 merge 到代码当中。这样在构建的时候,就会有就已经有了相关的翻译资料在代码里面。

还有一种做法是动态地做。一方面,可以去远程的翻译库“拉取”内容,这种情况在网站流量大的时候,可能会有性能问题。但是好处是,翻译永远是最新的。另一方面,想要做优化的话,可以采取“推送”的方式,每次翻译库有新改动,触发一个 webhook 来把内容推到服务器上。

在我看来,维护翻译会比添加翻译更加的繁琐。我曾经看到一些很大的项目,因为更新翻译之后没能够及时的删除老的翻译,导致翻译库过于的庞大,整个项目变得乱七八糟。这个时候如果有一个好的工具,能够保证数据的一致性。会对清洁的代码,有非常大的帮助。

阿里巴巴的 Kiwi 国际化全流程解决方案就做了 linter 和 VS Code 插件来帮助你检查和抽取代码中的翻译。

时间和时区

谈完了语言,接下来是时间和时区问题。因为是全球化的公司,所以说有很多数据是来自于全球、显示给全球的用户的。举个例子。国际航班在设置开始时间结束时间的时候如何保证这个时间在全局是一致的,并且在不同的时区会相应的显示。这非常的重要。同样的情况还应用于一切跟时间有关的事件,比如预定酒店、预定餐馆、安排会议。

首先时间有这样几种典型的表现形式。

  1. 自然语言,比如 07:23:01, 星期一 28, 十月 2019 CST AM/PM
  2. Unix timestamp (Int 类型),比如 1572218668
  3. Datetime. 注意 MySQL 存datetime 的时候会根据服务器时区转化成 UTC 然后存起来,读取的时候再转换回来。但是呢,服务器时区一般都是设置成 UTC 的。这种情况就是,存储不带时区,默认 UTC。
  4. ISO Date,比如 2019-10-27T23:24:28+00:00,这是带时区信息的。

我对这些形式没有大的偏好,你如果有相关经验,欢迎留言讨论。

具体在显示的时候。有两个可能出现的转化,一个是,从服务器存储的时区转化成当地时区显示的形式;另外一个是,语言上会由机器代码转换成自然语言。后一步流行的做法是使用强大的处理时间和日期的库,比如 moment.jsdayjs

数字与货币

不同国家区域对于数值的显示,其实是天差地别的。数值中间的逗号和点,在不同的国家,有不同的含义。

(1000.1).toLocaleString("en")
// => "1,000.1"
(1000.1).toLocaleString("de")
// => "1.000,1"
(1000.1).toLocaleString("ru")
// => "1 000,1"

阿拉伯数字并不是在所有区域都通用的,比如 Java 的 String.format 中 1、2、3这种数字在真正的阿拉伯语言里,使用的数字是١、٢、٣

价格方面,同样的货物,在不同的国家地区,是否要显示成当地货币的价值?货币的符号是什么?货币能够精确到哪一位?这些问题统统要先做好准备。

总结

本文提到的国际化工具有,翻译管理系统,开源的 Mozilla Pontoon、闭源收费的 lokalise.co,POEditor.com 等等。代码上的一致性 阿里巴巴 Kiwi 国际化全流程解决方案。UI 显示上的 moment.js, day.js

如同一切软件系统的开发一样,国际化这件事情没有银弹,好的作品都是靠基本功一点一滴磨出来的。

设计负载均衡器

· 阅读需 4 分钟

需求分析

互联网服务往往要处理来自全世界的流量,但是,一个服务器只能够同时服务有限数量的请求。因此,通常我们会有一个服务器集群来共同处理这些流量。那么问题来了,怎样才能够让这些流量均匀地分布到不同的服务器上呢?

从用户到服务器,会经过很多的节点和不同层级的负载均衡器。具体来讲,我们这次设计的需求是:

  • 设计第7层的负载均衡器,位于数据中心的内部。
  • 利用来自后端实时的负载信息。
  • 服务每秒千万级的流量以及10 TB每秒级别的吞吐量。

补充:如果服务 A 依赖服务 B,那我们称 A 是 B 的下游服务,而 B 是 A 的上游服务。

挑战

为什么负载均衡会很难做?答案是很难收集准确的负载分布数据。

按照数量分布 ≠ 按照负载分布

最简单的做法是根据请求的数量,随机地或者循环地分布流量。然而,实际的负载并不是根据请求的数量来算的,比如有些请求很重很耗CPU,有些请求很轻量级。

为了更加准确地衡量负载,负载均衡器得保持一些本地状态 —— 比如,存当前的请求数、连接数、请求处理的延迟。基于这些状态,我们能够使用相应的负载均衡的算法 —— 最少连接、最少延迟、随机 N 取一。

最少连接:请求会被导向当前连接数最小的服务器。

最少延迟:请求会被导向最少平均反应时长且最少连接数的服务器。还可以给服务器加权重。

随机 N 取一 (N 通常是 2,所以我们也可以称之为二选一的力量):随机的选两个服务器,取两者之中最好的,能够避免最坏的情况。

分布式的环境

在分布式的环境中,本地的负载均衡器难移了解上下游服务完整的状态,包括

  • 上游服务的负载
  • 上游服务可能超级大,因此很难选择一个合适的子集接入负载均衡器
  • 下游服务的负载
  • 不同种类的请求的具体处理时间很难预测

解决方案

有三种方案能够准确地搜集负载的具体情况并相应地处理:

  • 中心化的一个均衡器,根据情况动态地处理
  • 分布式但是各个均衡器之间要共享状态
  • 服务器返回请求的时候捎带上负载信息,或者是均衡器主动询问服务器

Dropbox 在做 Bandai 的时候选择了第三种方案,因为这很好地适应了现行的随机 N 选一的算法。

然而,与原配的随机 N 选一的算法所不同的是,不是使用本地的状态,而是选择服务器实时返回的结果。

服务器使用率:后端服务器设置了最大负载,数当前的连接,然后计算出使用率,范围是从 0.0 到 1.0.

有两个问题需要考虑:

  1. 处理错误: 如果 fail fast ,由于处理得很快,反而会吸引更多的流量产生更多的错误。
  2. 数据要衰减: 如果服务器的负载太高,没有请求会发到那里。因此,使用一个类似于反 S 曲线的衰减函数来保证老数据会被清理掉。

结果: 服务器接收的请求更加的均衡了

Lyft 的营销自动化平台 Symphony

· 阅读需 3 分钟

获客效率问题:广告投放如何花更少的钱用更少的人得到更高回报?

具体来讲,Lyft 的广告投放要服务如下特点

  1. 管理基于地域的 campaign
  2. 数据驱动的增长:增长必须是规模化的、可测量的、可预测的
  3. 支撑起 Lyft 独特的增长模型,如图:

lyft growth model

主要的挑战是:难以规模化管理跨地域营销中的各个环节,广告竞标、预算、素材、激励、选择受众、测试等等。下图是营销者的一天:

营销者的一天

我们可以发现“执行”占去了大部分的时间,而更少的时间花在了更重要的“分析和决策”上。规模化意味着减少繁复的操作,让营销人员专注于分析与决策。

解决方案:自动化

为了降低成本,提高做实验的效率,需要

  1. 预测新用户是否对产品感兴趣
  2. 多渠道优化,有效评估和分配预算
  3. 方便地管理上千个 campaigns

数据由 Lyft 的 Amundsen 系统做增强学习。

自动化的部分包括:

  1. 更新 bid 的关键词
  2. 关掉效果不好的素材
  3. 根据市场改变 referrals values
  4. 找到高价值的用户 segment
  5. 在多个 campaign 中共享策略

构架

Lyft Symphony Architecture

技术栈:Apache Hive, Presto, ML platform, Airflow, 3rd-party APIs, UI.

具体的组成模块

LTV 预测模块

用户的终身价值是衡量渠道的重要标准,预算由 LTV 和我们愿意为该地区的获客付出的价格共同决定。

我们对新用户的认知有限,随着交互的增多,所提供的历史记录会更准确地预测。

一开始的特征值:

特征值

随着历史上的交互记录的积累,做出的判断就会越准确:

根据历史记录判断 LTV

预算分配模块

搞定了 LTV,接下来是根据价格定预算。拟合出 LTV = a * (spend)^b 形式的曲线以及周围的区间里类似参数的曲线。为了找到全局最优,需要付出一些随机性的代价。

预算计算

投放模块

分为两部分,一部分是调参者,一部分是执行者。调参者根据定价,设定基于渠道的具体的参数;执行者把这些参数执行到具体的渠道上。

有很多流行的投放策略,在各色的渠道中,是共通的:

投放策略

总结

要注意人的经验在系统中的重要性,否则会 garbage in, garbage out. 当人从繁琐的投放任务解放出来,专注于理解用户、理解渠道、理解自身要传达给受众的信息之后,就能够获得更好的投放效果——花更少的时间达到更高的 ROI。

如何编写稳健的代码?

· 阅读需 1 分钟

他喜欢它

  1. 同理心 / 视角转换是最重要的。

    1. 意识到代码首先是为人类阅读而编写的,然后才是为机器执行。
    2. 软件是如此“柔软”,有很多方法可以实现同一目标。关键在于做出适当的权衡,以满足需求。
    3. 发明与简化:Apple Pay RFID 与 微信扫码二维码。
  2. 选择可持续的架构,以减少每个功能的人力资源成本。

  1. 采用模式和最佳实践。

  2. 避免反模式

    • 缺少错误处理
    • 回调地狱 = 意大利面条代码 + 不可预测的错误处理
    • 过长的继承链
    • 循环依赖
    • 过于复杂的代码
      • 嵌套的三元操作
      • 注释掉未使用的代码
    • 缺少国际化,特别是 RTL 问题
    • 不要重复自己
      • 简单的复制粘贴
      • 不合理的注释
  3. 有效的重构

    • 语义版本
    • 永远不要对非主要版本引入破坏性更改
      • 两腿变更

构架入门

· 阅读需 4 分钟

什么是构架

构架是软件系统的形状。拿建筑物来举例子:

  • 范式 paradigm 是砖块
  • 设计原则是房间
  • 组件是建筑

他们共同服务于一个特定的目的,就像医院治疗病人,学校教育学生一样。

我们为什么需要架构?

行为 vs. 结构

每一个软件系统提供两个不同的价值给利益相关者:行为与结构。软件开发者必须确保这两项价值都要高

==由于其工作的需要,软件架构师更多地聚焦于系统的结构而不是特性和功能。==

终极目标——==减少每加一个新特性所需要耗费的人力成本==

架构服务于软件系统的整个生命周期,使其易于理解,开发,测试,部署和操作。 其目标是最小化每个业务用例的人力资源成本。

O’Reilly 出版的《软件架构》一书很好地介绍了这样五种基本的构架。

1.分层架构

分层架构是被广泛采用,也是被开发者所熟知的一种架构。因此,它也是应用层面上事实上的标准。如果你不知道应该使用什么架构,用分层架构就是不错的选择。

示例

  • TCP/IP模式:应用层 > 运输层 > 网际层 > 网络接口层
  • Facebook TAO网络层 > 缓存层(follower + leader) > 数据库层

优缺点:

  • 优点
    • 易于使用
    • 职责划分
    • 可测试性
  • 缺点
    • 庞大而僵化
      • 想要对架构进行调整、扩展或者更新就必须要改变所有层,十分棘手

2.事件驱动架构

任何一个状态的改变都会向系统发出一个事件。系统组件之间的通信都是经由事件完成的。

一个简化的架构包含中介(mdiator),事件队列(event queue)和通道(channel)。下图所示即为简化的事件驱动架构:

示例

  • QT:信号(signals)和槽(slots)
  • 支付基础设施:由于银行网关通常有较高的延迟,因此银行的架构中采用了异步技术

3.微核架构(aka Plug-in Architecture)

软件的功能被分散到一个核心和多个插件中。核心仅仅含有最基本的功能。各个插件之间互相独立并实现共享借口以实现不同的目标。

示例

  • Visual Studio Code 和 Eclipse
  • MINIX 操作系统

4.微服务架构

大型系统被解离成众多微服务,每一个都是单独部署的单位,他们之间通过RPCs进行通信。

uber architecture

示例

5.基于空间的架构

“基于空间的架构”这一名称来源于“元组空间”,“元组空间“有”分布式共享空间“的含义。基于空间的架构中没有数据库或同步数据库访问,因此该架构没有数据库的瓶颈问题。所有处理单元共享内存中应用数据副本。这些处理单元都可以很弹性地启动和关闭。

示例:详见 Wikipedia

  • 主要被使用Java的架构所采用:例如:JavaSpaces

今日头条推荐系统:P2 内容分析

· 阅读需 3 分钟

今日头条推荐系统:P1 概述 中,我们了解到内容分析和用户标签的数据挖掘是推荐系统的基石。

什么是内容分析?

内容分析 = 从原始文章和用户行为中提取中间数据。

以文章为例。为了建模用户兴趣,我们需要对内容和文章进行标记。为了将用户与“互联网”标签的兴趣关联起来,我们需要知道用户是否阅读了带有“互联网”标签的文章。

我们为什么要分析这些原始数据?

我们这样做的原因是 …

  1. 标记用户(用户画像)
    • 标记喜欢带有“互联网”标签的文章的用户。标记喜欢带有“小米”标签的文章的用户。
  2. 根据标签向用户推荐内容
    • 向带有“小米”标签的用户推送“小米”内容。向带有“Dota”标签的用户推送“Dota”内容。
  3. 按主题准备内容
    • 将“德甲”文章放入“德甲主题”。将“饮食”文章放入“饮食主题”。

案例研究:一篇文章的分析结果

以下是“文章特征”页面的示例。文章特征包括分类、关键词、主题、实体。

一篇文章的分析结果

一篇文章的分析结果:详细信息

文章特征是什么?

  1. 语义标签:人类预定义这些标签,具有明确的含义。

  2. 隐含语义,包括主题和关键词。主题特征描述了单词的统计数据。某些规则生成关键词。

  3. 相似性。重复推荐曾是我们从客户那里获得的最严重反馈之一。

  4. 时间和地点。

  5. 质量。滥用、色情、广告或“心灵鸡汤”?

文章特征的重要性

  • 并不是说没有文章特征推荐系统就完全无法工作。亚马逊、沃尔玛、Netflix可以通过协同过滤进行推荐。
  • 然而,在新闻产品中,用户消费的是当天的内容。没有文章特征的引导是困难的。协同过滤无法帮助引导。
    • 文章特征的粒度越细,启动的能力就越强。

更多关于语义标签的信息

我们将语义标签的特征分为三个层次:

  1. 分类:用于用户画像、过滤主题内容、推荐召回、推荐特征
  2. 概念:用于过滤主题内容、搜索标签、推荐召回(喜欢)
  3. 实体:用于过滤主题内容、搜索标签、推荐召回(喜欢)

为什么要分成不同的层次?我们这样做是为了能够以不同的粒度捕捉文章。

  1. 分类:覆盖全面,准确性低。
  2. 概念:覆盖中等,准确性中等。
  3. 实体:覆盖低,准确性高。它仅覆盖每个领域的热门人物、组织、产品。

分类和概念共享相同的技术基础设施。

我们为什么需要语义标签?

  • 隐含语义
    • 一直运作良好。
    • 成本远低于语义标签。
  • 但是,主题和兴趣需要一个明确的标签系统。
  • 语义标签还评估公司的NPL技术能力。

文档分类

分类层级

  1. 科学、体育、金融、娱乐
  2. 足球、网球、乒乓球、田径、游泳
  3. 国际、国内
  4. A队、B队

分类器:

  • SVM
  • SVM + CNN
  • SVM + CNN + RNN

计算相关性

  1. 对文章进行词汇分析
  2. 过滤关键词
  3. 消歧义
  4. 计算相关性

流处理和批处理框架

· 阅读需 2 分钟

为什么有这种框架?

  • 为了在更短的时间内处理更多的数据。
  • 统一处理分布式系统中的容错问题。
  • 将任务简化抽象以应对多变的业务要求。
  • 分别适用于有界数据集(批处理)和无界数据集(流处理)。

批处理与流处理的发展史简介

  1. Hadoop 与 MapReduce。谷歌让批处理在一个分布式系统中像 MapRduceresult = pairs.map((pair) => (morePairs)).reduce(somePairs => lessPairs)一样简单。
  2. Apache Storm 与有向图拓扑结构。MapReduce 不能很好地表示迭代算法。因此,内森·马兹(Nathan Marz)将流处理抽象成一个由 spouts 和 bolts 组件构成的图结构。
  3. Spark 内存计算。辛湜(Reynold Xin)指出 Spark 在处理相同数据的时候比 Hadoop 少使用十倍机器的同时速度却快三倍
  4. 基于 Millwheel 和 FlumeJava 的谷歌数据流(Google Dataflow)。谷歌使用窗口化API同时支持批处理与流处理。
  1. Flink 快速采纳了 ==Google Dataflow== 以及 Apache Beam 的编程模式。
  2. Flink 对 Chandy-Lamport checkpointing 算法的高效实现。

这些框架

架构选择

若要用商业机器来满足以上的需求,有这些热门的分布式系统架构……

  • master-slave(中心化的):Apache Storm + zookeeper, Apache Samza + YARN
  • P2P(去中心化的):Apache s4

功能

  1. DAG Topology 用来迭代处理 -例如Spark 中的 GraphX, Apache Storm 中的 topologies, Flink 中的 DataStream API。
  2. 交付保证 (Delivery Guarantees)。如何确保节点与节点之间数据交付的可靠性?至少一次 / 至多一次 / 一次。
  3. 容错性。使用cold/warm/hot standby, checkpointing 或者 active-active 来实现容错。
  4. 无界数据集的窗口化API。例如 Apache 的流式窗口。Spark 的Window函数。Apache Beam 的窗口化。

不同架构的区别对照表

架构StormStorm-tridentSparkFlink
模型原生微批量微批量原生
Guarentees至少一次一次一次一次
容错性记录Ack记录Ack检查点检查点
最大容错
延迟非常低
吞吐量

今日头条推荐系统:P1 概述

· 阅读需 4 分钟

我们优化的目标是什么?用户满意度

我们正在寻找以下最佳 函数 以最大化 用户满意度

用户满意度 = 函数(内容, 用户画像, 上下文)
  1. 内容:文章、视频、用户生成内容短视频、问答等的特征。
  2. 用户画像:兴趣、职业、年龄、性别和行为模式等。
  3. 上下文:工作空间、通勤、旅行等场景下的移动用户。

如何评估满意度?

  1. 可测量的目标,例如:

    • 点击率
    • 会话时长
    • 点赞
    • 评论
    • 转发
  2. 难以测量的目标:

    • 广告和特殊类型内容(问答)的频率控制
    • 低俗内容的频率控制
    • 减少点击诱饵、低质量、恶心内容
    • 强制/固定/高度权重重要新闻
    • 低权重来自低级账户的内容

如何优化这些目标?机器学习模型

寻找上述最佳 函数 是一个典型的监督机器学习问题。为了实现系统,我们有以下算法:

  1. 协同过滤
  2. 逻辑回归
  3. 深度神经网络
  4. 因子分解机
  5. GBDT

一个世界级的推荐系统应该具备 灵活性,能够进行 A/B 测试并结合上述多种算法。现在结合逻辑回归和深度神经网络的做法越来越流行。Facebook 多年前就同时使用了逻辑回归和 GBDT。

模型如何观察和测量现实?特征工程

  1. 内容特征与用户兴趣之间的相关性。 显性相关性包括关键词、类别、来源、类型。隐性相关性可以从用户向量或模型如因子分解机中的物品向量中提取。

  2. 环境特征,如地理位置、时间。 可以作为偏差或在其基础上建立相关性。

  3. 热门趋势。 有全球热门趋势、类别热门趋势、主题热门趋势和关键词热门趋势。热门趋势在我们对用户信息较少时非常有助于解决冷启动问题。

  4. 协同特征,有助于避免推荐内容越来越集中。 协同过滤不是单独分析每个用户的历史,而是根据用户的点击、兴趣、主题、关键词或隐性向量找到用户之间的相似性。通过找到相似用户,可以扩展推荐内容的多样性。

大规模实时训练

  • 用户喜欢看到根据我们从他们的行为中跟踪到的信息实时更新的新闻推送。
  • 使用 Apache Storm 实时训练数据(点击、展示、收藏、分享)。
  • 收集数据直到达到阈值,然后更新推荐模型
  • 在高性能计算集群中存储模型参数,如数百亿的原始特征和数十亿的向量特征。

它们的实现步骤如下:

  1. 在线服务实时记录特征。
  2. 将数据写入 Kafka
  3. 从 Kafka 向 Storm 进件数据
  4. 填充完整的用户画像并准备样本
  5. 根据最新样本更新模型参数
  6. 在线建模获得新知识

如何进一步减少延迟?召回策略

考虑到所有内容的超大规模,无法用模型预测所有事情。因此,我们需要召回策略来关注数据的代表性子集。性能在这里至关重要,超时为 50 毫秒。

召回策略

在所有召回策略中,我们采用 反向索引<Key, List<Article>>

Key 可以是主题、实体、来源等。

兴趣标签相关性文档列表
电子商务0.3
娱乐0.2
历史0.2
军事0.1

数据依赖

  • 特征依赖于用户端和内容端的标签。
  • 召回策略依赖于用户端和内容端的标签。
  • 用户标签的内容分析和数据挖掘是推荐系统的基石。

设计优步打车服务

· 阅读需 2 分钟

免责声明:以下所有内容均来自公共资源或纯粹原创。 这里不包含优步的涉密内容。

要求

  • 为全球的交通运输市场提供服务
  • 大规模的实时调度
  • 后端设计

架构

uber architecture

为何要微服务?

==Conway定律== 软件的系统结构会对应企业的组织结构。

单体 ==服务==微服务
当团队规模和代码库很小时,生产力✅ 高❌ 低
==当团队规模和代码库很大时,生产力==❌ 低✅ 高 (Conway定律)
==对工程质量的要求==❌ 高 (能力不够的开发人员很容易破坏整个系统)✅ 低 (运行时是隔离的)
dependency 版本升级✅ 快 (集中管理)❌ 慢
多租户支持 / 生产-staging状态隔离✅ 容易❌ 困难 (每项服务必须 1) 要么建立一个staging环境连接到其他处于staging状态的服务 2) 要么跨请求上下文和数据存储的多租户支持)
可调试性,假设相同的模块,参数,日志❌ 低✅ 高 (如果有分布式tracing)
延迟✅ 低 (本地)❌ 高 (远程)
DevOps成本✅ 低 (构建工具成本高)❌ 高 (容量规划很难)

结合单体 ==代码库== 和微服务可以同时利用两者的长处.

调度服务

  • 由geohash提供一致的哈希地址
  • 数据在内存中是瞬态的,因此不需要复制. (CAP: AP高于CP)
  • 分片中使用单线程或者锁,以防止双重调度

支付服务

==关键是要有异步设计==, 因为跨多个系统的ACID交易支付系统通常具有非常长的延迟.

用户档案服务和行程记录服务

  • 使用缓存降低延迟
  • 随着 1) 支持更多的国家和地区 2) 用户角色(司机,骑手,餐馆老板,食客等)逐渐增加,为这些用户提供用户档案服务也面临着巨大的挑战。

通知推送服务

  • 苹果通知推送服务 (不可靠)
  • 谷歌云消息服务GCM (它可以检测到是否成功递送) 或者
  • 短信服务通常更可靠

再窥iOS架构模式

· 阅读需 3 分钟

我们为什么要在架构上费心思?

答案是:为了减少在每做一个功能的时候所耗费的人力资源

移动开发人员会在以下三个层面上评估一个架构的好坏:

  1. 各个功能分区的职责分配是否均衡
  2. 是否具有易测试性
  3. 是否易于使用和维护
职责分配的均衡性易测试性易用性
紧耦合MVC
Cocoa MVC❌ V和C是耦合的✅⭐
MVP✅ 独立的视图生命周期一般:代码较多
MVVM一般:视图(View)存在对UIKit的依赖一般
VIPER✅⭐️✅⭐️

紧耦合MVC

传统 MVC

举一个例子,在一个多页面的网页Web应用程序中,当你点击一个链接导航至其他页面的时候,该页面就会被全部重新加载。该架构的问题在于视图(View)与控制器(Controller)和模型(Model)是紧密耦合的。

Cocoa MVC

Cocoa MVC 是苹果公司建议iOS开发者使用的架构。理论上来说,该架构可以通过控制器(Controller)将模型(Model)与视图(View)剥离开。

Cocoa MVC

然而,在实际操作过程中,Cocoa MVC 鼓励大规模视图控制器的使用,最终使得视图控制器完成所有操作。

实际的 Cocoa MVC

尽管测试这样的耦合大规模视图控制器是十分困难的,然而在开发速度方面,Cocoa MVC是现有的这些选择里面表现最好的。

MVP

在MVP中,Presenter与视图控制器(view controller)的生命周期没有任何关系,视图可以很轻易地被取代。我们可以认为UIViewController实际上就是视图(View)。

MVC 的变体

还有另外一种类型的MVP:带有数据绑定的MVP。如下图所示,视图(View)与模型(Model)和控制器(Controller)是紧密耦合的。

MVP

MVVM

MVVM与MVP相似不过MVVM绑定的是视图(View)与视图模型(View Model)。

MVVM

VIPER

不同于MV(X)的三层结构,VIPER具有五层结构(VIPER View, Interactor, Presenter, Entity, 和 Routing)。这样的结构可以很好地进行职责分配但是其维护性较差。

VIPER

相较于MV(X),VIPER有下列不同点:

  1. Model 的逻辑处理转移到了 Interactor 上,这样一来,Entities 没有逻辑,只是纯粹的保存数据的结构。
  2. ==UI相关的业务逻辑分在Presenter中,数据修改功能分在Interactor中==。
  3. VIPER为实现模块间的跳转功能引入了路由模块 Router 。

如何使用HTTP协议向移动设备传输视频? HTTP Live Streaming (HLS)

· 阅读需 1 分钟

为什么需要这样一个协议?

使用HTTP直播流的移动视频播放服务有以下问题:

  1. ==手机的内存和外存有限==。
  2. 受制于不稳定的网络连接以及不同的带宽,需要==在传输过程中动态调整传输视频的质量==。

解决方法

  1. 服务器层面:在典型的设置中,编码硬件接受音视频输入,将其编码为H.264格式的视频和ACC格式的音频,然后将他们以MPEG-2格式流输出。
    1. 其后通过一个软件分流器将原始输出流分割为一系列短媒体文件(长度可能为10s的.ts文件)。
    2. 分流器同时也会维护一个包含所有媒体文件列表的索引文件(.m3u8格式)。
    3. 将以上步骤生成的媒体文件和索引文件发布在网络服务器上。
  2. 客户端层面:客户端读取索引,然后向服务器顺序请求所需要的媒体文件,并且流畅地将各个短媒体文件的内容播放出来。

架构

HLS Architecture