最近的一段时间,我在Reddit上看到一篇引起了我注意的帖子,标题是:
“PayPal如何仅用8台虚拟机扩展至每日处理数十亿交易”。
我对这个让人吃惊的壮举感到十分地好奇,便深入到网络中,不断阅读PayPal工程师的博客与各种文章。
我的目标是揭示这一惊人成就后面的“魔法”。
而今天,我很高兴在这篇文章中与各位分享我的发现。但是,这里有个转折 —— 我们不仅要谈论它,我们还会深入到Go语言代码中,以实际的操作来理解使这一显著里程碑成为可能的技术。
因此,让我们一起开始这次美好旅程,探索PayPal如何仅用8台虚拟机应对十亿交易的挑战。
1998年12月,美国加利福尼亚州帕洛阿尔托
一个工程师团队(Max Levchin, Joe Lubin, 和 Zach Simons)创建了一个在线支付服务,他们将其命名为PayPal。
在初期,产品经历了人气的暴增,并吸引了越来越多的用户,支付平台遭遇了大幅度的流量增长。为了应对这种高涨的需求,并确保用户体验无缝,这个团队采取有策略地投资,并购买了新的硬件以便扩展。
然而,快速的增长并没有放慢。仅在接下来的两年里,PayPal 达到了每日一百万笔交易的里程碑。为了应对这种爆炸性的增长,他们通过在超过1000台虚拟机中运行服务,以此来扩大自己的基础设施。
虽然这一举动成功地解决了平台的可扩展性挑战,但也引入了新的复杂性和问题,这些问题源于需要管理一个更大且更复杂的系统。
以下是PayPal 在扩展到每日超过一百万笔交易时面临的一些产品与技术挑战:
网络基础设施
请求的增加意味着每一个请求需要更多的网络跳转才能完成,这会导致延迟加剧。
此外,维护扩展后的网络基础设施变得更加昂贵。
2. 维护成本
向系统添加更多服务器增加了其整体复杂性。在每台机器上部署服务需要更多时间,设置自动扩展需要额外的努力。基础设施管理任务,如监控,变得更具有挑战性。
3. 资源使用
尽管系统已经做到了扩展,但它们并没有充分利用每台服务器的CPU。简单来说,服务器的吞吐量低,导致资源浪费以及增加了额外成本。
那么,如何应对这些挑战?
采用Actor模型
以下是PayPal应对挑战的方法:
PayPal认识到他们现有的代码在利用硬件能力方面是低效的,因此将简单性和可扩展性作为优先考虑的目标。
为了实现这一目标,他们转向了基于Akka框架的actor模型。
这种范式转变使得并行处理成为可能,优化了硬件使用。
Actor模型作为并行计算的概念框架,其中计算的基本单位被称为actor。下面是actor模型如何提供极端的可扩展性:
Actor作为轻量级对象,比线程消耗更少的资源,可以轻松创建数百万个actor。线程被动态分配给actor进行消息处理,每个线程依次处理多个actor。线程数量随CPU核心数量扩展,有效管理大量并发的actor。
高效的资源利用
Actor以其轻量级特性而闻名,比线程消耗更少的资源,使得可以轻松创建数百万个actor。
线程会动态分配给actor用于顺序处理消息,线程数量与CPU核数成比例扩展,有效管理大量并发的actor。
隔离状态管理
Actor保持着隔离和私有的状态,避免了共享内存。Actor之间的通信通过网络传输简单、不可变的消息来进行。
每个actor都有一个信息箱,作为FIFO消息队列并用于流式处理。在应用服务器中存储本地状态,可以最小化对分布式缓存或数据库的网络调用,提高整体系统性能。
另外,PayPal使用一致性哈希来将客户端路由到同一服务器。
有效的并发处理
Actor模型也促进多个actor的同时执行,每个actor依次处理消息。
此种方法确保每个actor一次只管理一个消息,从而促进并行。异步操作消除了等待响应的需要,简化了并发管理。
PayPal利用Akka的函数式编程风格来实现可扩展性,防止副作用并方便开发测试。
可插拔式代码片段进一步简化了可扩展性,允许actor在本地或远程无缝运行,无需显式的系统感知。
健壮的容错机制:
Actor在维护容错性方面起着关键作用,通过创建和监督其它actor。如果一个actor失败,监督者要么重新启动该actor,要么将消息重定向到另一个actor。
错误传递到监督者,确保优雅的错误处理,而无需不必要的代码复杂性。所以,actor模型建立了一个弹性的框架,用于管理系统内的故障。
因此最终的成果,PayPal成功地以非常显著的效率管理了每日十亿笔交易,而只使用了8台虚拟机。
这一举动不仅解决了之前的挑战,还展示了一个设计良好的并发模型在增强可扩展性和系统性能方面的力量。
现在理论部分说得足够多了——让我们卷起袖子开干,深入到一些Go代码中去揭示Actor模型,并见证其实际的实现。
Go中的Actor模型
Go语言中的actor模型是一种并发模型,将actor视为独立的实体,每个actor都有自己的状态和行为。Go语言通过goroutines和channels提供了轻量级的并发机制,可以用来实现actor模型。
在这个上下文中,actor是一个并发、独立的计算单元,通过消息传递与其他actor通信。Goroutines可以用来表示actor,channels则促进它们之间的通信。每个actor都有自己的状态,并异步处理消息。
要在Go语言中实现actor模型,你通常会为每个actor创建一个goroutine,并使用channels在actor之间发送消息。这些消息将包含关于接收actor的预期动作或行为的信息。
下面用简单的代码来说明此概念:
package main
import (
"fmt"
"sync"
)
// Actor represents an actor with its own state and a channel for receiving messages.
type Actor struct {
state int
mailbox chan int
}
// NewActor creates a new actor with an initial state.
func NewActor(initialState int) *Actor {
return &Actor{
state: initialState,
mailbox: make(chan int),
}
}
// ProcessMessage processes a message by updating the actor's state.
func (a *Actor) ProcessMessage(message int) {
fmt.Printf("Actor %d processing message: %dn", a.state, message)
a.state += message
}
// Run simulates the actor's runtime by continuously processing messages from the mailbox.
func (a *Actor) Run(wg *sync.WaitGroup) {
defer wg.Done()
for {
message := <-a.mailbox
a.ProcessMessage(message)
}
}
// System represents the actor system managing multiple actors.
type System struct {
actors []*Actor
}
// NewSystem creates a new actor system with a given number of actors.
func NewSystem(numActors int) *System {
system := &System{}
for i := 1; i <= numActors; i++ {
actor := NewActor(i)
system.actors = append(system.actors, actor)
go actor.Run(nil)
}
return system
}
// SendMessage sends a message to a randomly selected actor in the system.
func (s *System) SendMessage(message int) {
actorIndex := message % len(s.actors)
s.actors[actorIndex].mailbox <- message
}
func main() {
// Create an actor system with 3 actors.
actorSystem := NewSystem(3)
// Send messages to the actors concurrently.
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(message int) {
defer wg.Done()
actorSystem.SendMessage(message)
}(i)
}
// Wait for all messages to be processed.
wg.Wait()
}
这个程序创建了一个简单的actor系统,包含三个actor。消息并发式地发送给actor,每个actor根据收到的消息更新其状态。该程序演示了actor模型的隔离、并发处理和消息传递原则。
下面是由提供的Go代码生成的输出:
Actor 3 processing message: 5
Actor 8 processing message: 2
Actor 1 processing message: 3
Actor 2 processing message: 1
Actor 3 processing message: 4
注意:请记住特定的输出可能会根据goroutine执行情况而变化。这只是一个基本示例,在实际场景中,你可能需要处理actor的终止、监督以及actor模型等其他方面。
事实上,有类似的开源库,如:
github.com/AsynkronIT/protoactor-go
这样的库在Go语言中提供了更高级的actor模型实现。
结语
在这篇文章中,我们探讨了PayPal如何仅用8台虚拟机处理每日数十亿笔交易。
我们从PayPal的早期开始,讲述他们在人气飙升时所面临的挑战。为了应对增加的需求,他们投资新硬件,但很快就达到每天一百万笔交易。解决方案是什么?他们扩大到超过1000台虚拟机。然而,这带来了新问题,如网络带宽问题和高昂成本。
然后又出现了Actor模型——一种酷炫的管理方式。简单来说,就像有演员(或工人)通过传递消息相互交谈。这样帮助PayPal变得超级高效,我们解释了它是如何通过轻量级的actor和智能并发工作的。
为了让事情更有意思,我们编写了一个简单的Go代码来展示Actor模型在行动中是如何工作的:代码创建actor,向它们发送消息,并让它们做自己的事情。
现在,本次旅程完美结束了,我希望你对像PayPal这样的系统如何处理大任务有了更清晰的认识。
请继续关注我们对科技和系统世界的更多冒险!
作者:场长
参考:
https://medium.com/@nidhey29/how-did-paypal-handle-a-billion-daily-transactions-with-eight-virtual-machines-76b09ce5455c