Paypal工程博客

20180328 Paypal工程博客 copy


草原

1月30日 20:30來自iPhone客戶端

PayPal 嘗試用 akka stream 和 akka actor 來處理交易来屠,這給他們帶來了一個從更自然的角度來重新審視業(yè)務(wù)模型的機會,并且瘟芝,將處理時間縮短了 80% 以及獲得了接近 0 的失敗率: Learnings from Using a Reactive Platform – Akka/Squbs | PayPal Engineering BlogO網(wǎng)頁鏈接

??? 草原老師推薦,Squbs是產(chǎn)品infoQ有介紹


Learnings from Using a Reactive Platform – Akka/Squbs

By Balaji Srinivasaraghavan December 13, 2017

Introduction

Simple and reliable APIs are important for winning customer trust in

all industries. This is especially true when the API is a customer

facing endpoint that accepts payment instructions. Add to that the

ability to submit 15,000 payments in a single request and reliability

becomes critical.

Let’s talk a bit aboutPayouts– a PayPal product through which users can submit mass payment requests. It’s an asynchronous interaction – a request with multiple payment instructions returns a token. The caller can then use the token to poll for the payment outcomes or listen for webhooks.

As you can imagine, customers like to be sure we got each request

right and want to be kept up to date on the status of the money

movements. The actual request processing involves orchestrating calls to

a slew of services ranging from ones that provide information on

recipients to services that perform the actual money movement.

We recently finished work on improving the features offered by the product and on further improving its reliability. One of the changes we made was to useAkkato build the payment orchestration engine as a data flow pipeline. Discussed below are some learnings from that exercise.




Concurrency – Harder Than It Needs to Be?

Object-oriented design does a great job of capturing the static

aspects of a real world system with entities, their attributes and

relationships. These aspects can be translated directly to Java

artifacts – classes with variables using inheritance and composition.

Real world entities also have a dynamic nature to them – the way in

which they come to life over the lifetime of the application and how

they interact with each other. Java unfortunately does not have a

sufficiently nuanced abstraction for representing such changes in states

of entities over time or their degree of concurrency. Instead, we have a

CPU’s (hardware) model of the world – threads and processors, that has

no equivalent in the real world. This results in an impedance mismatch

that the programmer is left to reconcile.

Some improvements in the building blocks for representing concurrent

aspects came with Java 8. While we didn’t get a better model, we did get

some elegant constructs that reduced the pain of using Java for such

tasks.

Lambdas made multithreaded Java code actually readable. Java 8 introduced the ability to compose futures.

And, we got streams. Streams trace their roots back to a programming

paradigm from the 1960s called data flow programming. The model lets us

compose a series of transformations on a dataset as a pipeline. While we

concentrate on the logical composition of a data processing job, the

framework is tasked with figuring out the physical orchestration of

parallel processing for the job.

Java’s implementation of streams was designed with the expectation

that it would not have to deal with high latency (typically IO). This

makes it unsuitable for building services. But, reactive frameworks such

as Akka provide implementations that account for high latency tasks

such as service invocations within a stream.

Data flow programmingusing streams and theactor modelprovide great conceptual models for reasoning about the dynamic aspects of a real world system. These models represent a paradigm shift once adopted. We can use them to represent concurrency using abstractions that are intuitive. The same model can be represented in Java as well and brought to life by the underlying platform, without developers having to manually code for concurrency using lower level primitives.



Learnings From Using Reactive Streams and the Actor Model

To set the context, the primary design

goal of the system is to reliably accept and process payments. That

means we maintain an accurate record of progress and prioritize features

such as check pointing & auto-recovery over response time and

throughput. That does not mean the solution can be “slow” though. The

difference in emphasis only means we need to get more done in the same

amount of time.

Below are some of the learnings we had over the course of the project as it relates to reactive streams and using the actor model. The ideas come from a variety of sources including other teams who had used Akka before us, the team which maintainsSqubs(Akka with PayPal operationalization) as well as our own experience.

Foundational Patterns for Reuse

Over time, there are a bunch of stream patterns that recur throughout

the application. It helps to create a pattern library as we encounter

them.

Select between two flows based on a predicate

Select one of many flows based on a decider function

Bypass running a flow and short circuit if an error was encountered in a previous stage

Retry an operation based on a decider (see one from Squbshere)

Akka provides some patterns as well –akka-stream-contrib

An example of a flow selector that selects one of many flows based on a decider function would be:

public static Flow selectByIndex(ActorSystem system, Function numericPartitioner, Flow... innerFlows) {

return Flow.fromGraph(GraphDSL.create(builder -> {

? // given a data element in the stream, return the index of the flow to be used to process it

? UniformFanOutShape top = builder.add(Partition.create(innerFlows.length, msg -> {

? ? int partition = numericPartitioner.apply(msg);

? ? return partition;

? }));

? // merge the output of the individual flows back to the primary flow

? // this is made possible by the use of a homogeneous envelope type across the flow

? akka.stream.scaladsl.MergePreferred.MergePreferredShape bottom = builder.add(MergePreferred.create(innerFlows.length - 1));

? int index = 0;

? for (Flow flow : innerFlows) {

? FlowShape tgt = builder.add(flow);

? builder.from(top.out(index)).toInlet(tgt.in());

? //last flow is the preferred flow

? builder.from(tgt.out()).toInlet((index + 1 == innerFlows.length) ? bottom.preferred() : bottom.in(index));

? index++;

}

return FlowShape.of(top.in(), bottom.out());

}));

}

While individually most of these helpers are not complex to create,

they save a lot of effort for the team over time. This is especially

true for ones such as a predicate based selector that show up

everywhere. These otherwise would have to be written as graph stages.

With helpers, we can now use flow composition to put them to use and

focus on writing and certifying the business logic alone.



Failure is the Message

It helps a great deal to use a homogenous data type to transfer

information on success and failure down the stream without aborting

processing. To achieve that, we model stream elements as envelopes with

business information and metadata about the status of previous

transformations.

For example, say we could not reach a service as part of a stream

stage. We can embed a Try or an optional exception in the current

message when that happens. A subsequent transformation may choose to not

run and short-circuit on seeing the failure while a stage that updates

the database could choose to participate to record the outcome. A stream

stage’s behavior of not participating in processing if an exception was

recorded should be provisioned via flow composition rather than having

each stage run the same check.

Use Actors when Warranted

Akka streams should serve as the default model for most use cases.

Code modelled as a stream is not only performant but also simple to

create and maintain. But, sometimes actors provide advantages that are

difficult to achieve otherwise.

The primary use case for actors is, of course, to maintain state and

to perform tasks that benefit from supervision. Actors can work with a

single stream or across multiple streams for use cases such as

aggregation, performance or business statistics and even centralized

logging via an event bus. They are also a natural choice when you want

to separate processing of non-critical tasks away from a critical

pipeline. They are also useful for implementing blocking tasks (on a

separate thread pool) especially if we don’t need feedback on the

outcome.



Stream Decomposition Using Actors

There are advantages in decomposing a large stream into smaller

streams that are anchored in chained actors. We used such a model to

reduce the solution complexity while servicing the requirement to

checkpoint and auto-recover. Each of those actors act as a checkpoint

from where processing can resume in case of a failure.

We can model check pointing within the stream as well. But, using

chained actors with smaller and more focused streams made the code

simpler to write and easier to maintain. This is because the branching

introduced by features such as check pointing are orthogonal to business

logic. They compound the complexity already being expressed in the

stream and tend to not be amenable to local optimizations like flow

composition.

If you are designing for high velocity or performance, you are going

to want to use a single stream. But, for most other applications,

leveraging actors and streams together can help simplify the design of

complex streams.



Platform Lock-in and Portability

If we can provide a reasonable level of isolation between the actual

business logic and the code that strings them together and orchestrates

the flow, we can retain a good amount of flexibility in moving to a

different implementation of reactive streams or the actor model.

One area that needs more attention though is Akka HTTP. One of Akka’s

key strengths as a reactive platform is the way in which Akka HTTP is

tied in with Akka Streams. Together they deliver a lot of value for

developers as a simple cohesive package. But, in the context of

portability, it makes sense for us to build a layer of abstraction on

top of the Akka HTTP client. This serves two purposes –

Airgap developers from Akka HTTP to enable changing the HTTP client provider at a later point in time

A layer of indirection enables provisioning a richer set of features that’s comparable to JAX-RS – marshalling/unmarshalling, custom retry. The flip side is we lose advanced features such as client sideHTTP response streaming.



Conclusion

We’re in the midst of a significant transformation in the way we

write Java applications. Functional programming in Java 8 is fairly well

understood by now.? Reactive programming, given the massive adoption

Java streams had, has established itself as indispensable. Reactive

streams have been added to the Java 9 SDK.

Broadly, these are 3 class of applications that benefit from adopting a reactive framework.

Backend services – those with multiple downstream dependencies (micro-services) or with a need to orchestrate complex tasks

Engineering teams who want to leverage modern frameworks that

abstract complexity in traditional programing – multithreading,

non-blocking IO

Asynchronous or high velocity event streams – twitter, mobile application GUI

Akka is built for performance and ease of use. That’s a hard balance

to achieve. It’s even harder to achieve on your own. Whether it’s

concurrency or non-blocking IO, the Akka toolkit provides intuitive and

performant models for developers to build upon.

In our case, we saw an 80% reduction in processing time with the new

stack and a near 0% failure rate. Surely, as we turn on more features,

there’s more to learn and improve. But, it’s clear that adopting

reactive streams and the actor model has enabled us to service our

customers better by providing a more reliable and performant API.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末褥琐,一起剝皮案震驚了整個濱河市锌俱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敌呈,老刑警劉巖贸宏,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異磕洪,居然都是意外死亡吭练,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門析显,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲫咽,“玉大人,你說我怎么就攤上這事谷异》质” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵歹嘹,是天一觀的道長箩绍。 經(jīng)常有香客問我,道長尺上,這世上最難降的妖魔是什么材蛛? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮尖昏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘构资。我一直安慰自己抽诉,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布吐绵。 她就那樣靜靜地躺著迹淌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪己单。 梳的紋絲不亂的頭發(fā)上唉窃,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音纹笼,去河邊找鬼纹份。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔓涧。 我是一名探鬼主播件已,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼元暴!你這毒婦竟也來了篷扩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茉盏,失蹤者是張志新(化名)和其女友劉穎鉴未,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸠姨,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡铜秆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了享怀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羽峰。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖添瓷,靈堂內(nèi)的尸體忽然破棺而出梅屉,到底是詐尸還是另有隱情,我是刑警寧澤鳞贷,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布坯汤,位于F島的核電站,受9級特大地震影響搀愧,放射性物質(zhì)發(fā)生泄漏惰聂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一咱筛、第九天 我趴在偏房一處隱蔽的房頂上張望搓幌。 院中可真熱鬧,春花似錦迅箩、人聲如沸溉愁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拐揭。三九已至,卻和暖如春奕塑,著一層夾襖步出監(jiān)牢的瞬間堂污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工龄砰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盟猖,地道東北人讨衣。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像扒披,于是被迫代替她去往敵國和親值依。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容