跳到主要内容

31 篇博文 含有标签「system design」

查看所有标签

企业授权服务 2022

· 阅读需 8 分钟

授权决定个人或系统是否可以访问特定资源。这个过程是一个典型的场景,可以通过软件进行自动化。我们将回顾谷歌的 Zanzibar、受 Zanzibar 启发的解决方案以及市场上的其他 AuthZ 服务。

Zanzibar:谷歌的一致性全球授权系统

  • 谷歌的 = 经受住了谷歌产品的考验,每秒检查 2000 万个权限,p95 < 10 毫秒,99.999% 的可用性
  • 一致性 = 确保授权检查基于不超过客户端指定更改的 ACL 数据
  • 全球 = 地理分布的数据中心,并在全球数千台服务器上分配负载。
  • 授权 = 通用授权

在 Zanzibar 的背景下,我们可以这样表达 AuthZ 问题:

isAuthorized(user, relation, object) = 用户与对象之间是否存在关系?

这被称为基于关系的访问控制(==ReBAC==)。客户端可以在 ReBAC 的基础上构建 ABAC 和 RBAC。不幸的是,Zanzibar 不是开源的,也不能作为现成的服务购买。

Zanzibar 架构

Zanzibar 架构

为什么 Zanzibar 可扩展?

  • 使用 Spanner 作为数据库
  • Leopard 索引系统
    • 将组到组的路径展平,类似于图中的可达性问题
    • 将索引元组存储为整数的有序列表,使用如跳表等结构,以实现集合之间的高效并集和交集。
    • 异步数据流客户端 > aclserver > changelog > Leopard 索引系统
  • 如何维护外部一致性?Zookie 协议 - 客户端使用基于时间戳的令牌检查权限。

Auth0 细粒度授权 (FGA)

Auth0 FGA 是谷歌 Zanzibar 的 开源实现。请查看互动教程 https://zanzibar.academy/。

对于微服务背景下的企业开发者,如何使用 FGA 的托管解决方案?

如何使用 FGA?

  1. 访问 FGA 仪表板,以 DSL 和关系元组定义授权模型,最后添加像自动化测试一样的授权断言(这太棒了!)。
  2. 开发者返回他们的服务并调用 FGA 包装器的检查端点。

不幸的是,我没有看到变更日志审计和版本控制,以便在开发者在 FGA 仪表板中出错时进行回滚,可能是因为 FGA 仍在进行中。

OSO

使用 Oso,您可以:

  • 建模:使用 Oso 的内置原语设置常见的权限模式,如基于角色的访问控制(RBAC)和关系。根据需要使用 Oso 的声明式策略语言 Polar(DSL)进行扩展。
  • 过滤:超越是/否的授权问题。也对集合实施授权 - 例如,“只显示 Juno 可以看到的记录。”
  • 测试:在您拥有单一接口的情况下,现在可以编写单位测试来验证您的授权逻辑。使用 Oso 调试器或 REPL 来追踪意外行为。

Ory Keto

Keto 是 Zanzibar 的开源(Go)实现。提供 gRPC、REST API、新 SQL 和易于使用的细粒度权限语言(DSL)。支持 ACL、RBAC 和其他访问模型。

Authzed SpiceDB

SpiceDB 是一个开源数据库系统,用于管理受安全保护的应用程序权限,灵感来自谷歌的 Zanzibar 论文。

Aserto Topaz

Topaz 是一个开源授权服务,为应用程序和 API 提供细粒度、实时、基于策略的访问控制。

它使用 Open Policy Agent(OPA)作为决策引擎,并提供一个受谷歌 Zanzibar 数据模型启发的内置目录。

授权策略可以利用用户属性、组成员资格、应用程序资源及其之间的关系。所有用于授权的数据都在嵌入式数据库中建模和存储,因此可以快速高效地评估授权决策。

Cloudentity

这似乎是一个集成的 CIAM 解决方案,没有独立的企业授权功能。文档令人困惑...

Open Policy Agent

Open Policy Agent (OPA) 是一个开源的通用政策引擎,统一了整个堆栈的政策执行。OPA 提供了一种高级声明性语言,让您可以将政策指定为代码,并提供简单的 API,将政策决策从您的软件中卸载。您可以在微服务、Kubernetes、CI/CD 管道、API 网关等中使用 OPA 来执行政策。

OPA 最初由 Styra 创建,并且是 Cloud Native Computing Foundation (CNCF) 的一个毕业项目。

Permit.IO

Permit.IO 是一个基于 OPA 和 OPAL 的低代码 AuthZ 平台。

Scaled Access

Scaled Access 是一家欧洲公司,已被 onewelcome 收购。它提供丰富的上下文感知访问控制、实时政策执行、细粒度授权和基于关系的访问控制。文档中有 API,但没有 SDK。

Casbin

Casbin 是一个授权库,支持 Golang 中的 ACL、RBAC、ABAC 等访问控制模型。它在多种编程语言中提供 SDK。然而,它的配置在 CSV 文件中相对静态,更适合企业内部使用,而不适合面向客户的授权。

SGNL

这个服务看起来相当简陋 - 美丽的网站却没有任何开发者内容。没有文档,没有视频或自助演示。我怀疑它的定位是面向非技术企业。不推荐。

总结

这是我初步检查后的排名。理想情况下,我希望有一个类似 LaunchDarkly 的 AuthZ 平台 - 易于集成和操作,配备完整的审计日志、版本控制和面向开发者的网页门户。


Github StarsModelsDevExPerfScore (out of 5)
Oso2.8kReBACDSL, API, SDK, web portal?3
Spicedb3kReBACDSL, API, SDK, web portal?3
permit.io840ReBACDSL, API, SDK, low-code web portal?3
Aserto Topas534ReBACDSL, API, SDK, web portal?3
FGA657ReBACDSL, API, SDK, web portal?3
Keto3.8kReBACDSL, API, SDK?2
Casbin13.4kABAC, RBACLibrary, static file for policies?1

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

· 阅读需 10 分钟

需求分析

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

构架设计

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

基础层(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) 工程方案

· 阅读需 14 分钟

需求分析

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

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

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

设计负载均衡器

· 阅读需 5 分钟

需求分析

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

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

  • 设计第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

· 阅读需 5 分钟

获取效率问题:如何在广告中实现更好的投资回报率?

具体来说,Lyft 的广告应满足以下要求:

  1. 能够管理区域特定的广告活动
  2. 以数据驱动的增长为指导:增长必须是可扩展的、可衡量的和可预测的
  3. 支持 Lyft 独特的增长模型,如下所示

lyft growth model

然而,最大挑战是管理跨区域营销的所有流程,包括选择竞标、预算、创意、激励和受众,进行 A/B 测试等。您可以看到数字营销人员一天的工作:

营销者的一天

我们发现 执行 占用了大部分时间,而 分析,被认为更重要的,所花的时间要少得多。一个扩展策略将使营销人员能够专注于分析和决策过程,而不是操作活动。

解决方案:自动化

为了降低成本并提高实验效率,我们需要

  1. 预测新用户对我们产品感兴趣的可能性
  2. 有效评估并在各渠道分配营销预算
  3. 轻松管理成千上万的广告活动

营销绩效数据流入 Lyft 的强化学习系统:Amundsen

需要自动化的问题包括:

  1. 更新搜索关键词的竞标
  2. 关闭表现不佳的创意
  3. 按市场更改推荐值
  4. 识别高价值用户细分
  5. 在活动之间共享策略

架构

Lyft Symphony Architecture

技术栈包括 - Apache Hive、Presto、ML 平台、Airflow、第三方 API、UI。

主要组件

生命周期价值(LTV)预测器

用户的生命周期价值是衡量获取渠道效率的重要标准。预算由 LTV 和我们愿意在该地区支付的价格共同决定。

我们对新用户的了解有限。历史数据可以帮助我们在用户与我们的服务互动时更准确地进行预测。

初始特征值:

特征值

随着互动历史数据的积累,预测会得到改善:

根据历史记录判断 LTV

预算分配器

在预测 LTV 之后,接下来是根据价格估算预算。一个形式为 LTV = a * (spend)^b 的曲线拟合数据。在成本曲线创建过程中将注入一定程度的随机性,以便收敛到全局最优解。

预算计算

竞标者

竞标者由两个部分组成 - 调整器和执行者。调整器根据价格决定特定渠道的确切参数。执行者将实际竞标传达给不同的渠道。

一些在不同渠道应用的流行竞标策略如下所示:

投放策略

结论

我们必须重视自动化过程中的人类经验;否则,模型的质量可能会是“垃圾进,垃圾出”。一旦从繁重的任务中解放出来,营销人员可以更多地专注于理解用户、渠道以及他们想要传达给受众的信息,从而获得更好的广告效果。这就是 Lyft 如何以更少的时间和精力实现更高的投资回报率。

Lyft 的营销自动化平台 Symphony

· 阅读需 4 分钟

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

具体来讲,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。

设计股票价格变动的智能通知

· 阅读需 16 分钟

需求

  • 300 万用户
  • 5000 支股票 + 250 支全球股票
  • 用户在以下情况下会收到价格变动通知:
    1. 订阅该股票
    2. 股票价格变动达到 5% 或 10%
    3. 自 a) 上周或 b) 昨天以来
  • 可扩展性。可能支持其他类型的通知,如突发新闻、财报电话会议等。

构建架构草图

上下文:

  • 什么是清算?清算是金融交易结算的程序——即资金及时准确地转移给卖方,证券转移给买方。通常,清算由一个称为清算所的专业组织作为中介。
  • 什么是证券交易所?一个股票经纪人和交易者可以买卖证券的场所。

苹果推送通知服务


(APNs)

苹果推送通知服务<br>(APNs)

Google Firebase 云消息传递


(FCM)

Google Firebase 云消息传递<br>(FCM)

电子邮件服务


AWS SES /sendgrid/etc

电子邮件服务<br>AWS SES /sendgrid/etc

通知者

通知者

外部供应商



市场价格

[不支持查看器]

Robinhood 应用

Robinhood 应用

API 网关

API 网关

反向代理

反向代理

批量写入

批量写入

价格


监测器

[不支持查看器]

时间序列数据库


influx 或 prometheus

时间序列数据库<br>influx 或 prometheus

每 5 分钟一次

[不支持查看器]

定期读取

定期读取

价格


监测器

价格<br>监测器

用户设置

用户设置

通知队列

通知队列

限流缓存

限流缓存

cronjob

cronjob

这些组件是什么,它们如何相互作用?

  • 价格监测器
    • 数据获取策略
      • 选项 1 初步:每 5 分钟获取一次数据,并批量写入时间序列数据库。
      • 选项 2 高级:如今外部系统通常直接推送数据,因此我们不必一直拉取。
    • 每次请求或每次价格变动约 6000 个点。
    • 数据保留 1 周,因为这只是 lambda 架构的加速层。
  • 价格观察者
    • 读取过去一周或过去 24 小时内每只股票的数据。
    • 计算波动是否超过 5% 或 10% 在这两个时间段内。我们得到的元组如 (股票, 上涨 5%, 1 周)。
      • 边缘情况:我们是否应该规范化价格数据?例如,某人错误地将 UBER 以 1 美元的价格出售。
    • 速率限制(因为 5% 或 10% 的变化可能在一天内发生多次),然后发出事件 PRICE_CHANGE(STOCK_CODE, timeSpan, percentage) 到通知队列。
  • 定期触发器是 cron 作业,例如 Airflow、Cadence。
  • 通知队列
    • 在用户和股票数量较少时,可能不一定会引入。
    • 可能接受通用消息事件,如 PRICE_CHANGEEARNINGS_CALLBREAKING_NEWS 等。
  • 通知者
    • 订阅通知队列以获取事件
    • 然后从用户设置服务中获取通知对象
    • 最后根据用户设置,通过 APNs、FCM 或 AWS SES 发送消息。

构架入门

· 阅读需 5 分钟

什么是构架

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

  • 范式 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

流处理和批处理框架

· 阅读需 3 分钟

为什么有这种框架?

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

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

  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检查点检查点
最大容错
延迟非常低
吞吐量

使用半监督学习进行欺诈检测

· 阅读需 6 分钟

明确需求

实时计算风险概率分数,并结合规则引擎做出决策,以防止账户接管 (ATO) 和僵尸网络攻击。

通过在线和离线管道训练聚类特征

  1. 来源于网站日志、认证日志、用户行为、交易、监控列表中的高风险账户

  2. 在 Kafka 主题中跟踪事件数据

  3. 处理事件并准备聚类特征

实时评分和基于规则的决策

  1. 综合评估在线服务的风险评分

  2. 在规则引擎中手动配置以保持灵活性

  3. 在在线服务中共享或使用洞察

从易到难检测的账户接管

  1. 来自单个 IP

  2. 来自同一设备的 IP

  3. 来自全球的 IP

  4. 来自 100k 个 IP

  5. 针对特定账户的攻击

  6. 网络钓鱼和恶意软件

挑战

  • 手动特征选择

  • 对抗环境中的特征演变

  • 可扩展性

  • 无在线 DBSCAN

高级架构

核心组件和工作流程

半监督学习 = 未标记数据 + 少量标记数据

为什么?比无监督学习具有更好的学习准确性 + 比监督学习花费更少的时间和成本

训练:在数据库中准备聚类特征

  • Spark 上的流处理管道:

    • 实时连续运行。

    • 动态执行特征归一化和类别转换。

      • 特征归一化:将数值特征(如年龄、收入)缩放到 0 和 1 之间。

      • 类别特征转换:应用独热编码或其他转换,将类别特征转换为适合机器学习模型的数值格式。

    • 使用 Spark MLlib 的 K-means 将流数据聚类成组。

      • 运行 k-means 并形成聚类后,可能会发现某些聚类中有更多的欺诈实例。

      • 一旦根据历史数据或专家知识将某个聚类标记为欺诈,就可以在推断过程中使用该聚类分配。任何分配到该欺诈聚类的新数据点都可以标记为可疑。

  • 每小时定时任务管道:

    • 每小时定期运行(批处理)。

    • 应用 阈值 来根据聚类模型的结果识别异常。

    • 调整 DBSCAN 算法 的参数以改善聚类和异常检测。

    • 使用 scikit-learn 的 DBSCAN 在批量数据中寻找聚类并检测异常值。

      • DBSCAN 可以检测异常值,可能会识别出常规交易的聚类,并将其与可能不寻常、潜在欺诈的交易分开。

      • 在噪声或异常区域(不属于任何密集聚类的点)中的交易可以标记为可疑。

      • 在识别出某个聚类为欺诈后,DBSCAN 有助于即使在不规则形状的交易分布中也能检测到欺诈模式。

服务

服务层是将机器学习模型和业务规则转化为实际欺诈预防决策的地方。其工作原理如下:

  • 欺诈检测评分服务:

    • 提取来自传入请求的实时特征

    • 应用两种聚类模型(流处理中的 K-means 和批处理中的 DBSCAN)

    • 将分数与流计数器(如每个 IP 的登录尝试次数)结合

    • 输出 0 到 1 之间的统一风险评分

  • 规则引擎:

    • 作为系统的“头脑”

    • 将 ML 分数与可配置的业务规则结合

    • 规则示例:

      • 如果风险评分 > 0.8 且用户从新 IP 访问 → 需要 2FA

      • 如果风险评分 > 0.9 且账户为高价值 → 阻止交易

    • 规则存储在数据库中,可以无需代码更改进行更新

    • 为安全团队提供调整规则的管理门户

  • 与其他服务的集成:

    • 暴露用于实时评分的 REST API

    • 将结果发布到流计数器以进行监控

    • 将决策反馈到训练管道以提高模型准确性

  • 可观察性:

    • 跟踪关键指标,如误报/漏报率

    • 监控模型漂移和特征分布变化

    • 为安全分析师提供调查模式的仪表板

    • 记录详细信息以进行事后分析