仅用8台虚拟机处理每日数十亿笔交易?PayPal咋做到的?

Neo Kim 2024-08-22 15:22:26

 
 

“PayPal如何仅用8台虚拟机扩展至每日处理数十亿交易?”看到这篇文章的时候,被这一惊人壮举给震撼,现在一起看看具体的实践历程。

 
 

 

1998年12月,在美国的加利福尼亚州,一个工程师团队为手持设备开发安全软件,然而他们最开始的商业模式失败了。
 
于是,他们转而创建了一项在线支付服务,并将其命名为PayPal
 
PayPal的创立初期,用户就迎来了暴增,于是团队购置了更新的硬件来扩大规模。
 
然而,用户增长一直呈现爆炸性趋势,并在接下来的两年达到每天100万笔交易。因此,团需要通过1000多台虚拟机上运行服务,以实现扩展。

 

虽然他们解决了可扩展性,但是也带来了一些新的问题:

 

爆炸性增长带来的问题

 
 
1、网络架构扩展
 
随着请求量的增长,完成请求所需的网络传输路径变得更加复杂,导致处理延迟的增加。网络架构的扩展需求随之上升,这使得维护成本相应增加。
 
 
2、维护开销
 
随着更多服务器的加入,整个基础设施的复杂度也随之提高。服务器的部署和提供服务所需的时间变得更长。同时,实现基础设施的自动化扩展变得更加具有挑战性,监控和其他管理任务的难度也随之上升。
 
 
3、资源效率
 
服务器的CPU使用率下降,导致整体处理能力降低,这不仅造成了资源的浪费,同时也增加了运营成本。
 
如何解决这些问题?

 

采用Actor模型

 
PayPal认识到现有的代码在利用硬件能力方面是低效的,所以他们将简单性和可扩展性作为首要目标。
 
因此,他们转向了基于Akka框架的Actor模型。
 
 
1、Actor模型介绍
 
这种范式转变使得并行处理成为可能,且优化了硬件使用。
 
Actor模型作为并行计算的概念框架,其中计算的基本单位被称为actor。下面介绍Actor模型如何提升的可扩展性:
 
1)高效的资源利用
 
图片
 
Actor作为轻量级对象,相较于线程,其资源消耗更少,因此能够轻松地创建数百万个实例。线程会被动态地分配给这些Actor以处理消息,且每个线程会依次处理多个Actor的任务。线程的数量会根据CPU核心的数量进行扩展,从而有效地管理大量并发的Actor。
 
线程会被动态地分配给Actor,用于顺序处理消息。线程的数量会根据CPU核心的数量成比例地扩展,从而有效地管理大量的并发Actor。
 
2)隔离状态管理
 
在Actor模型中,每个实例都保持隔离和私有,避免共享内存资源。Actor之间通过发送简洁且不可变的数据包来实现通信,这些数据包通过网络进行传输。
 
图片
 
每个Actor都配备了一个消息队列,它按照先进先出(FIFO)的原则来处理消息流。在应用服务器上维护本地状态,这有助于减少对分布式缓存或数据库的网络请求,从而提高系统的整体性能。
 
PayPal采用了一致性哈希算法来确保客户端能够被路由到相同的服务器,以保持会话的连续性。
 
3)并发处理的高效性
 
Actor模型支持多个Actor的并行操作,每个Actor独立地按顺序处理其接收到的消息。这种方法确保了在任何给定时间,每个Actor只处理一个消息,从而优化了并行处理。异步消息处理机制消除了等待响应的需求,简化了并发控制。
 
图片
 
PayPal利用Akka框架的函数式编程特性,增强了应用的可扩展性,避免了副作用,并简化了开发和测试过程。Actor模型的可插拔代码片段进一步简化了系统的扩展,允许Actor在本地或远程环境中无缝运行,无需复杂的系统配置。
 
3)容错机制的健壮性
 
Actor模型通过创建和监督其他Actor来维护系统的容错性。如果一个Actor失败,其监督者可以选择重新启动该Actor或将消息转发给其他Actor。
 
图片
 
错误信息会传递给监督者,这保证了错误处理的优雅性,同时避免了代码的不必要复杂化。因此,Actor模型建立了一个弹性的系统框架,用于有效管理内部故障。
 
最终,PayPal通过Actor模型成功地以极高的效率处理了每天十亿笔交易,仅使用了少量的虚拟机资源。这一成果不仅解决了先前的技术挑战,还展示了精心设计的并发模型在提升系统可扩展性和性能方面的潜力。
 
现在,我们已经对理论有了足够的了解——让我们开始实际编码,深入了解Go语言中的Actor模型,并观察其在实际应用中的表现。
 
 
2、Go中的Actor模型
 
Go语言提供了一种并发编程范式,即Actor模型,它将程序分解为独立的Actor单元。每个Actor拥有自己的状态和行为逻辑。Go语言特有的goroutines和channels机制,为实现轻量级的Actor模型提供了便利。
 
在这种模型下,Actor被视为自包含的并发单元,它们通过交换消息来实现相互之间的通信。Goroutines作为Actor的实现方式,而channels则用于Actor间的信息传递。每个Actor独立维护自己的状态,并且能够异步地处理接收到的消息。
 
要在Go中应用Actor模型,常见的做法是为每个Actor分配一个goroutine,并通过channels来传递消息,从而实现Actor之间的交互。这些消息将指导接收Actor执行特定的操作或行为。
 
以下是一个简单的Go程序示例,用以展示Actor模型的基本概念:
 
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

		
package mainimport ( "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: %d\n", 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: 5Actor 8 processing message: 2Actor 1 processing message: 3Actor 2 processing message: 1Actor 3 processing message: 4
 
请注意,由于goroutines的执行顺序可能不同,具体的输出结果可能会有所变化。这里提供的只是一个基础示例。在实际应用中,你可能还需要考虑Actor的生命周期管理、监督策略以及Actor模型的其他高级特性。
 
实际上,存在一些开源库,例如:github.com/AsynkronIT/protoactor-go,它们为Go语言提供了更为成熟的Actor模型实现方式。

 

结语

 

本文讲述了PayPal从早期到人气飙升增加虚拟机,到后来引入Actor并发模型以提升性能,解决了网络带宽及高昂成本的问题,实现仅用8台虚拟机处理每日数十亿笔交易的实践历程。
 
希望借此实践,让大家了解支付系统处理大规模任务的一些经验,也欢迎大家再评论区进行交流。

 
作者丨Neo Kim 
来源丨网址:https://medium.com/@nidhey29/how-did-paypal-handle-a-billion-daily-transactions-with-eight-virtual-machines-76b09ce5455c
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告