最近,我在reddit上看到一篇引起了我的注意的帖子:“PayPal如何仅用8台虚拟机扩展至每日处理数十亿交易”。对这个惊人的壮举感到好奇,我深入互联网的深处,阅读了PayPal工程师的博客和各种文章。我的目标是揭示这一成就背后的魔法。

今天,我很高兴在这个博客中与你分享我的发现。但这里有个转折 —— 我们不仅要谈论它,我们还会深入到Go代码中,以实际操作来理解使这一显著里程碑成为可能的技术。

所以,让我们一起开始这次旅程,探索PayPal如何仅用八台虚拟机应对十亿交易挑战。

1998年12月 —— 美国加利福尼亚州帕洛阿尔托

一个由工程师团队(Max Levchin, Joe Lubin, 和 Zach Simons)创建了一个在线支付服务,并将其命名为PayPal。

在初期,他们经历了人气的激增,吸引了越来越多的用户,平台遭遇了大幅度的流量增长。为了应对这种高涨的需求并确保用户体验无缝,他们有策略地投资并购买了新的硬件以扩展。

然而,快速的增长并没有放慢。仅在接下来的两年里,他们就达到了每日一百万笔交易的显著里程碑。为了应对这种爆炸性的增长,他们通过在超过1000台虚拟机中运行服务来扩大他们的基础设施。

虽然这一举动成功地解决了他们的可扩展性挑战,但它引入了新的复杂性和问题,这些问题源于管理一个更大且更复杂的系统。

以下是他们在扩展到每日超过一百万笔交易时面临的一些挑战:

1. 网络基础设施:

需求增加意味着一个请求需要更多的网络跳转才能完成,导致延迟加剧。此外,维护扩展后的网络基础设施变得更加昂贵。

2. 维护成本:

向系统添加更多服务器增加了其整体复杂性。在每台机器上部署服务需要更多时间,设置自动扩展需要额外的努力。基础设施管理任务,如监控,变得更具挑战性。

3. 资源使用:

尽管扩展了,但他们并没有充分利用每台服务器的CPU。简单来说,服务器的吞吐量低,导致资源浪费和额外成本。

那么他们是如何应对这些挑战的呢?

采用Actor模型

以下是他们如何应对挑战的:

PayPal认识到他们现有的代码在利用硬件能力方面的低效性,因此将简单性和可扩展性作为优先考虑的目标。为了实现这一目标,他们转向了基于Akka框架的actor模型。

这种范式转变使得并行处理成为可能,优化了硬件使用。

Actor模型作为并行计算的概念框架,其中计算的基本单位被称为actor。下面是actor模型如何提供极端的可扩展性:

Actor作为轻量级对象,比线程消耗更少的资源,允许轻松创建数百万个actor。线程被动态分配给actor进行消息处理,每个线程依次处理多个actor。线程数量随CPU核心数量扩展,有效管理大量并发的actor。

高效的资源利用:

Actor以其轻量级特性而闻名,比线程消耗更少的资源,使得可以轻松创建数百万个actor。

如何用八台虚拟机处理每日十亿笔交易?PayPal是怎么做到的?插图

线程会动态分配给actor用于顺序处理消息,线程数量与CPU核数成比例扩展,有效管理大量并发的actor。

隔离状态管理:

Actor保持隔离和私有的状态,避免了共享内存。Actor之间的通信通过网络传输简单、不可变的消息来进行。

如何用八台虚拟机处理每日十亿笔交易?PayPal是怎么做到的?插图1

每个actor都有一个邮箱,作为FIFO消息队列用于流线型处理。在应用服务器中存储本地状态可以最小化对分布式缓存或数据库的网络调用,提高整体系统性能。

此外,PayPal使用一致性哈希来将客户路由到同一服务器。

有效的并发处理:

Actor模型促进了多个actor的同时执行,每个actor依次处理消息。这种方法确保每个actor一次只管理一个消息,从而促进并行。异步操作消除了等待响应的需要,简化了并发管理。

如何用八台虚拟机处理每日十亿笔交易?PayPal是怎么做到的?插图2

PayPal利用Akka的函数式编程风格来实现可扩展性,防止副作用并方便测试。插入可插拔代码片段进一步简化了可扩展性,允许actor在本地或远程无缝运行,无需显式的系统感知。

健壮的容错机制:

Actor在维护容错性方面起着关键作用,通过创建和监督其他actor。如果一个actor失败,监督者要么重新启动该actor,要么将消息重定向到另一个actor。

如何用八台虚拟机处理每日十亿笔交易?PayPal是怎么做到的?插图3

错误传播到监督者确保了优雅的错误处理,而无需不必要的代码复杂性。因此,actor模型建立了一个弹性的框架,用于管理系统内的故障。

结果,PayPal成功地以显著的效率管理了每日十亿笔交易,只使用了8台虚拟机。这一举动不仅解决了之前的挑战,还展示了一个设计良好的并发模型在增强可扩展性和系统性能方面的力量。

现在,理论部分说得够多了——让我们卷起袖子,深入到一些Go代码中去揭示Actor模型,并见证其实际实现。

Go中的Actor模型

Go语言中的actor模型是一种并发模型,将actor视为独立的实体,每个actor都有自己的状态和行为。GoLang通过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: %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: 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如何仅用八台虚拟机处理每日数十亿笔交易。我们从PayPal的早期开始,讲述他们在人气飙升时所面临的挑战。为了应对增加的需求,他们投资新硬件,但很快就达到每天一百万笔交易。解决方案是什么?他们扩大到超过1000台虚拟机。然而,这带来了新问题,如网络问题和高成本。

然后出现了Actor模型——一种酷炫的管理方式。简单来说,就像有演员(或工人)通过传递消息相互交谈。这帮助PayPal变得超级高效,我们解释了它是如何通过轻量级的actor和智能并发工作的。

为了让事情更有趣,我们甚至编写了一个简单的Go代码来展示Actor模型在行动中是如何工作的。代码创建actor,向它们发送消息,并让它们做自己的事情。

现在,这次旅程结束了,我希望你对像PayPal这样的系统如何处理大任务有了更清晰的认识。请继续关注我们对科技和系统世界的更多冒险!



如何用八台虚拟机处理每日十亿笔交易?PayPal是怎么做到的?插图4

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:https://choupangxia.com/2024/01/13/paypal-structure/