Tải bản đầy đủ (.pdf) (84 trang)

IT training ebook reactive microservices the evolution of microservices at scale 2 khotailieu

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (6.06 MB, 84 trang )

Co
m
pl
im
en

Jonas Bonér

of

The Evolution of Microservices at Scale

ts

Reactive
Microsystems



Reactive Microsystems

The Evolution of Microservices at Scale

Jonas Bonér

Beijing

Boston Farnham Sebastopol

Tokyo



Reactive Microsystems
by Jonas Bonér
Copyright © 2017 Lightbend, Inc. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA
95472.
O’Reilly books may be purchased for educational, business, or sales promotional use.
Online editions are also available for most titles ( For more
information, contact our corporate/institutional sales department: 800-998-9938 or


Editor: Brian Foster
Production Editor: Melanie Yarbrough
Copyeditor: Octal Publishing Services
Proofreader: Matthew Burgoyne
August 2017:

Interior Designer: David Futato
Cover Designer: Karen Montgomery
Illustrator: Rebecca Demarest

First Edition

Revision History for the First Edition
2017-08-07: First Release
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Reactive Microsys‐
tems, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.
While the publisher and the author have used good faith efforts to ensure that the
information and instructions contained in this work are accurate, the publisher and

the author disclaim all responsibility for errors or omissions, including without limi‐
tation responsibility for damages resulting from the use of or reliance on this work.
Use of the information and instructions contained in this work is at your own risk. If
any code samples or other technology this work contains or describes is subject to
open source licenses or the intellectual property rights of others, it is your responsi‐
bility to ensure that your use thereof complies with such licenses and/or rights.

978-1-491-99433-7
[LSI]


Table of Contents

Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
1. Essential Traits of an Individual Microservice. . . . . . . . . . . . . . . . . . . . 1
Isolate All the Things
Single Responsibility
Own Your State, Exclusively
Stay Mobile, but Addressable

1
2
3
6

2. Slaying the Monolith. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Don’t Build Microliths

9


3. Microservices Come in Systems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Embrace Uncertainty
We Are Always Looking into the Past
The Cost of Maintaining the Illusion of a Single Now
Learn to Enjoy the Silence
Avoid Needless Consistency

11
12
13
13
14

4. Events-First Domain-Driven Design. . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Focus on What Happens: The Events
Think in Terms of Consistency Boundaries
Manage Protocol Evolution

17
21
25

5. Toward Reactive Microsystems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Embrace Reactive Programming
Embrace Reactive Systems
Microservices Come as Systems

28
35
44

iii


6. Toward Scalable Persistence. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Moving Beyond CRUD
Event Logging—The Scalable Seamstress
Transactions—The Anti-Availability Protocol

49
50
59

7. The World Is Going Streaming. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Three Waves Toward Fast Data
Leverage Fast Data in Microservices

68
68

8. Next Steps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Further Reading
Start Hacking

iv

|

Table of Contents

71

72


Introduction

The Evolution of Scalable Microservices
In this report, I will discuss strategies and techniques for building
scalable and resilient microservices, working our way through the
evolution of a microservices-based system.
Beginning with a monolithic application, we will refactor it, briefly
land at the antipattern of single instance—not scalable or resilient—
microliths (micro monoliths), before quickly moving on, and step
by step work our way toward scalable and resilient microservices
(microsystems).
Along the way, we will look at techniques from reactive systems,
reactive programming, event-driven programming, events-first
domain-driven design, event sourcing, command query responsibil‐
ity segregation, and more.

v


We Can’t Make the Horse Faster
If I had asked people what they wanted, they would have said faster
horses.
—Henry Ford1

Today’s applications are deployed to everything from mobile devices
to cloud-based clusters running thousands of multicore processors.
Users have come to expect millisecond response times (latency) and

close to 100 percent uptime. And, by “user,” I mean both humans
and machines. Traditional architectures, tools, and products as such
simply won’t cut it anymore. We need new solutions that are as dif‐
ferent from monolithic systems as cars are from horses.
Figure P-1 sums up some of the changes that we have been through
over the past 10 to 15 years.

Figure P-1. Some fundamental changes over the past 10 to 15 years
To paraphrase Henry Ford’s classic quote: we can’t make the horse
faster anymore; we need cars for where we are going.
So, it’s time to wake up, time to retire the monolith, and to decom‐
pose the system into manageable, discrete services that can be scaled
individually, that can fail, be rolled out, and upgraded in isolation.

1 It’s been debated whether Henry Ford actually said this. He probably didn’t. Regardless,

it’s a great quote.

vi

|

Introduction


They have had many names over the years (DCOM, CORBA, EJBs,
WebServices, etc.). Today, we call them microservices. We, as an
industry, have gone full circle again. Fortunately, it is more of an
upward spiral as we are getting a little bit better at it every time
around.


We Need to Learn to Exploit Reality
Imagination is the only weapon in the war against reality.
—Lewis Carroll, Alice in Wonderland

We have been spoiled by the once-believed-almighty monolith—
with its single SQL database, in-process address space, and threadper-request model—for far too long. It’s a fairytale world in which
we could assume strong consistency, one single globally consistent
“now” where we could comfortably forget our university classes on
distributed systems.
Knock. Knock. Who’s There? Reality! We have been living in this
illusion, far from reality.
We will look at microservices, not as tools to scale the organization
and the development and release process (even though it’s one of the
main reasons for adopting microservices), but from an architecture
and design perspective, and put it in its true architectural context:
distributed systems.
One of the major benefits of microservices-based architecture is that
it gives us a set of tools to exploit reality, to create systems that
closely mimic how the world works.

Don’t Just Drink the Kool-Aid
Everyone is talking about microservices in hype-cycle speak; they
are reaching the peak of inflated expectations. It is very important to
not just drink the Kool-Aid blindly. In computer science, it’s all
about trade-offs, and microservices come with a cost. Microservices
can do wonders for the development speed, time-to-market, and
Continuous Delivery for a large organization, and it can provide a
great foundation for building elastic and resilient systems that can


Introduction

|

vii


take full advantage of the cloud.2 That said, it also can introduce
unnecessary complexity and simply slow you down. In other words,
do not apply microservices blindly. Think for yourself.

2 If approached from the perspective of distributed systems, which is the topic of this

report.

viii

|

Introduction


CHAPTER 1

Essential Traits of an Individual
Microservice

In my previous book, Reactive Microservices Architecture, I discussed
the essential traits of a microservice: isolation, autonomicity, single
responsibility, exclusive state, and mobility. Let’s take a few minutes

to recap the essence of these traits.

Isolate All the Things
Without great solitude, no serious work is possible.
—Pablo Picasso

Isolation is the most important trait and the foundation for many of
the high-level benefits in microservices.
Isolation also has the biggest impact on your design and architec‐
ture. It will, and should, slice up the entire architecture, and there‐
fore it needs to be considered from day one.
It will even affect the way you break up and organize the teams and
their responsibilities, as Melvyn Conway discovered in 1967 (later
named Conway’s Law):
Any organization that designs a system (defined broadly) will pro‐
duce a design whose structure is a copy of the organization’s com‐
munication structure.

Isolation between services makes it natural to adopt Continuous
Delivery (CD). This makes it possible for you to safely deploy appli‐

1


cations and roll out and revert changes incrementally, service by ser‐
vice.
Isolation makes it easier to scale each service, as well as allowing
them to be monitored, debugged, and tested independently—some‐
thing that is very difficult if the services are all tangled up in the big
bulky mess of a monolith.


Act Autonomously
In a network of autonomous systems, an agent is only concerned with
assertions about its own policy; no external agent can tell it what to do,
without its consent. This is the crucial difference between autonomy and
centralized management.
—Mark Burgess, Promise Theory

Isolation is a prerequisite for autonomy. Only when services are iso‐
lated can they be fully autonomous and make decisions independ‐
ently, act independently, and cooperate and coordinate with others
to solve problems.
Working with autonomous services opens up flexibility around ser‐
vice orchestration, workflow management, and collaborative behav‐
ior, as well as scalability, availability, and runtime management, at
the cost of putting more thought into well-defined and composable
APIs.
But autonomy cuts deeper, affecting more than the architecture and
design of the system. A design with autonomous services allows the
teams that build the services to stay autonomous relative to one
another—rolling out new services and new features in existing serv‐
ices independently, and so on.
Autonomy is the foundation on which we can scale both the system
and the development organization.

Single Responsibility
This is the Unix philosophy: Write programs that do one thing and do it
well. Write programs to work together.
—Doug McIlroy


2

|

Chapter 1: Essential Traits of an Individual Microservice


The Unix philosophy1 and design has been highly successful and still
stands strong decades after its inception. One of its core principles is
that developers should write programs that have a single purpose—a
small, well-defined responsibility, and compose it well so it works
well with other small programs.
This idea was later brought into the Object-Oriented Programming
community by Robert C. Martin and named the Single Responsibil‐
ity Principle2 (SRP), which states that a class or component should
“have only one reason to change.”
There has been a lot of discussion around the true size of a micro‐
service. What can be considered “micro”? How many lines of code
can it be and still be a microservice? These are the wrong questions.
Instead, “micro” should refer to scope of responsibility, and the
guiding principle here is the Unix philosophy of SRP: let it do one
thing, and do it well.
If a service has only one single reason to exist, providing a single
composable piece of functionality, business domains and responsi‐
bilities are not tangled. Each service can be made more generally
useful, and the system as a whole is easier to scale, make resilient,
understand, extend, and maintain.

Own Your State, Exclusively
Without privacy, there was no point in being an individual.

—Jonathan Franzen, The Corrections

Up to this point, we have characterized microservices as a set of iso‐
lated services, each one with a single area of responsibility. This
scheme forms the basis for being able to treat each service as a single
unit that lives and dies in isolation—a prerequisite for resilience—
and can be moved around in isolation—a prerequisite for elasticity
(in which a system can react to changes in the input rate by increas‐
ing or decreasing the resources allocated to service these inputs).

1 The Unix philosophy is described really well in the classic book The Art of Unix Pro‐

gramming by Eric Steven Raymond (Pearson Education).

2 For an in-depth discussion on the Single Responsibility Principle, see Robert C. Mar‐

tin’s website The Principles of Object Oriented Design.

Own Your State, Exclusively

|

3


Although this all sounds good, we are forgetting the elephant in the
room: state.
Microservices are most often stateful components: they encapsulate
state and behavior. Additionally, isolation most certainly applies to
state and requires that you treat state and behavior as a single unit.

They need to own their state, exclusively.
This simple fact has huge implications. It means that data can be
strongly consistent only within each service but never between serv‐
ices, for which we need to rely on eventual consistency and abandon
transactional semantics. You must give up on the idea of a single
database for all your data, normalized data, and joins across services
(see Figure 1-1). This is a different world, one that requires a differ‐
ent way of thinking and the use of different designs and tools—
something that we will discuss in depth later on in this report.

4

|

Chapter 1: Essential Traits of an Individual Microservice


Figure 1-1. A monolith disguised as a set of microservices is still a
monolith

Own Your State, Exclusively

|

5


Stay Mobile, but Addressable
To move, to breathe, to fly, to float, To gain all while you give, To roam
the roads of lands remote, To travel is to live.

—H. C. Andersen

With the advent of cloud computing, virtualization, and Docker
containers, we have a lot of power at our disposal to manage hard‐
ware resources efficiently. The problem is that none of these matter
if our microservices and their underlying platform cannot make
efficient use of them if they are statically locked into a specific topol‐
ogy or deployment scenario.
What we need are services that are mobile, allowing the system to be
elastic and adapt dynamically to its usage patterns. Mobility is the
possibility of moving services around at runtime while they are
being used. This is needed for the services to stay oblivious to how
the system is deployed and which topology it currently has—some‐
thing that can (and often should) change dynamically.
Now that we have outlined the five essential traits of an individual
microservice, we are ready to slay the monolith and put them to
practice.

6

| Chapter 1: Essential Traits of an Individual Microservice


CHAPTER 2

Slaying the Monolith

Only with absolute fearlessness can we slay the dragons of mediocrity
that invade our gardens.
—John Maynard Keynes


Before we take on the task of slaying the monolith, let’s try to under‐
stand why its architecture is problematic, why we need to “slay the
monolith” and move to a decoupled architecture using microservi‐
ces.
Suppose that we have a monolithic Java Platform, Enterprise Edition
(Java EE) application with a classic three-tier architecture that uses
Servlets, Enterprise Java Beans (EJBs) or Spring, and Java Persis‐
tence API (JPA), and an Oracle SQL database. Figure 2-1 depicts
this application.

7


Figure 2-1. A monolithic application with a classic three-tier architec‐
ture
The problem with this design is that it introduces strong coupling
between the components within each service and between services.
Workflow logic based on deep nested call chains of synchronous
method calls, following the thread of the request, leads to strong
coupling and entanglement of the services, making it difficult to
understand the system at large and to let services evolve independ‐
ently. The caller is held hostage until the methods have executed all
their logic.
Because all these services are tightly coupled, you need to upgrade
all of them at once. Their strong coupling also makes it difficult to
deal with failure in isolation. Exceptions—possibly blowing the
entire call stack—paired with try/catch statements is a blunt tool for
failure management. If one service fails, it can easily lead to cascad‐
ing failures across all of the tiers, eventually taking down the entire

application.
The lack of isolation between services also means that you can’t scale
each service individually. Even if you need to scale only one single
service (due to high traffic or similar), you can’t do that. Instead you
must scale the whole monolith, including all of its other services. In
the world of the monolith, it’s always all or nothing, leading to lack
of flexibility and inefficient use of resources.

8

|

Chapter 2: Slaying the Monolith


Application servers (such as WebLogic, JBoss, Tomcat, etc.) encour‐
age this monolithic model. They assume that you are bundling your
service JARs into an EAR (or WAR) file as a way of grouping your
services, which you then deploy—alongside all your other applica‐
tions and services—into the single running instance of the applica‐
tion server. The application server then manages the service
“isolation” through class loader magic. This is a fragile model, leav‐
ing services competing for resources like CPU time, main memory,
and storage space, resulting in reduced fairness and stability as a
result.

Don’t Build Microliths
microlith (\ˈmī-krə-ˌlith\):
—A very small stone tool made from a sharp blade-shaped piece
of stone.


Suppose that we want to move away from the application server and
the strongly coupled design and refactor this monolith into a
microservices-based system. By just drinking the Kool-Aid, relying
on a scaffolding tool, and following the path of least resistance,
many people end up with an architecture similar to that shown in
Figure 2-2.

Figure 2-2. A system of microliths communicating over synchronous
protocols

Don’t Build Microliths

|

9


In this architecture, we have single instance services communicating
over synchronous HTTP (often using RESTful1 APIs), running in
Docker containers, and using Create, Read, Update, and Delete
(CRUD) through JPA talking to a—hopefully dedicated—SQL data‐
base (in the worst case, still using a single monolithic database, with
a single, and highly normalized, schema for all services).
Well, what we have built ourselves is a set of micro monoliths—let’s
call them microliths.
A microlith is defined as a single-instance service in which synchro‐
nous method calls have been turned into synchronous REST calls
and blocking database access remains blocking. This creates an
architecture that is maintaining the strong coupling we wanted to

move away from but with higher latency added by interprocess
communication (IPC).
The problem with a single instance is that by definition it cannot be
scalable or available. A single monolithic thing, whatever it might be
(a human, or a software process), can’t be scaled out and can’t stay
available if it fails or dies.
Some people might think, “Well, Docker will solve that for me.” I’m
sorry to say, but containers alone won’t solve this problem. Merely
putting your microservice instances in Docker or Linux (LXC) con‐
tainers won’t help you as much as you would like.
There’s no question that containers and their orchestration manag‐
ers, like Kubernetes or Docker Swarm, are great tools for managing
and orchestrating hundreds of instances (with some level of isola‐
tion). But, when the dust settles, they have left you with the hard
parts of distributed systems. Because microservices are not isolated
islands and come in systems, the hardest parts are the space inbetween the services, in things like communication, consensus, con‐
sistency, and coordination to state and resources. These are
concerns that are a part of the application itself, not something that
can be bolted on after the fact.

1 Nothing in the idea of REST itself requires synchronous communication, but it is

almost exclusively used this way in the industry.

10

|

Chapter 2: Slaying the Monolith



CHAPTER 3

Microservices Come in Systems

One actor is no actor. Actors come in systems.
—Carl Hewitt

In the spirit of famed computer scientist Carl Hewitt:1 one microser‐
vice is no microservice. Microservices come in systems.
Like humans, microservices are autonomous and therefore need to
communicate and collaborate with others to solve problems. And as
with humans, it is in the collaboration with others that the most
interesting opportunities and challenging problems arise.
What’s difficult in microservices design is not creating the individ‐
ual services themselves, but managing the space between the serv‐
ices. We need to dig deeper into the study of systems of services.

Embrace Uncertainty
What is not surrounded by uncertainty cannot be the truth.
—Richard Feynman

As soon as we exit the boundary of the single-service instance, we
enter a wild ocean of nondeterminism—the world of distributed sys‐
tems—in which systems fail in the most spectacular and intricate
ways; where information becomes lost, reordered, and garbled; and
where failure detection is a guessing game.

1 Carl Hewitt invented the Actor Model in 1973.


11


It sounds like a scary world.2 But it is also the world that gives us
solutions for resilience, elasticity, and isolation, among others. What
we need is better tools to not just survive, but to thrive in the barren
land of distributed systems.

We Are Always Looking into the Past
The contents of a message are always from the past! They are never
“now.”
—Pat Helland

When it comes to distributed systems, one constraint is that commu‐
nication has latency.3 It’s a fact (quantum entanglement, wormholes,
and other exotic phenomena aside) that information cannot travel
faster than the speed of light, and most often travels considerably
slower, which means that communication of information has
latency.
In this case, exploiting reality means coming to terms with the fact
that information is always from the past, and always represents
another present, another view of the world (you are, for example,
always seeing the sun as it was 8 minutes and 20 seconds ago).
“Now” is in the eye of the beholder, and in a way, we are always
looking into the past.
It’s important to remember that reality is not strongly consistent,4 but
eventually consistent.5 Everything is relative and there is no single
“now.”6 Still, we are trying so hard to maintain the illusion of a single
globally consistent present, a single global “now.” This is no reason
to be surprised. We humans are bad at thinking concurrently, and


2 It is—if you have not experienced this first-hand, I suggest that you spend some time

thinking through the implications of L Peter Deutsch’s Fallacies of Distributed Com‐
puting.

3 That fact that information has latency and that the speed of light represents a hard (and

sometimes very frustrating) nonnegotiable limit on its maximum velocity is an obvious
fact for anyone that is building internet systems, or who has been on a VOIP call across
the Atlantic ocean.

4 Peter Bailis has a good explanation of the different flavors of strong consistency.
5 A good discussion on different client-side semantics of eventual consistency—includ‐

ing read-your-writes consistency and causal consistency—can be found in “Eventually
Consistent—Revisited” by Werner Vogels.

6 Justin Sheehy’s “There Is No Now” is a great read on the topic.

12

|

Chapter 3: Microservices Come in Systems


assuming full control over time, state, and causality makes it easier
to understand complex behavior.


The Cost of Maintaining the Illusion of a
Single Now
In a distributed system, you can know where the work is done or you
can know when the work is done but you can’t know both.
—Pat Helland

The cost of maintaining the illusion of a single global “now” is very
high and can be defined in terms of contention—waiting for shared
resources to become available—and coherency—the delay for data to
become consistent.
Gene Amdahl’s, now classic, Amdahl’s Law explains the effect that
contention has on a parallel system and shows that it puts a ceiling
on scalability, yielding diminishing returns as more resources are
added to the system.
However, it turns out that this is not the full picture. Neil Günter’s
Universal Scalability Law shows that when you add coherency to the
picture, you can end up with negative results. And, adding more
resources to the system makes things worse.
In addition, as latency becomes higher (as it does with distance), the
illusion cracks. The difference between the local present and the
remote past is even greater in a distributed system.

Learn to Enjoy the Silence
Words are very unnecessary. They can only do harm. Enjoy the silence.
—Martin Gore, Enjoy the Silence

Strong consistency requires coordination, which is very expensive in
a distributed system and puts an upper bound on scalability, availa‐
bility, low latency, and throughput. The need for coordination
means that services can’t make progress individually, because they

must wait for consensus.
The cure is that we need to learn to enjoy the silence. When design‐
ing microservices, we should strive to minimize the service-toservice communication and coordination of state. We need to learn
to shut up.
The Cost of Maintaining the Illusion of a Single Now

|

13


Avoid Needless Consistency
The first principle of successful scalability is to batter the consistency
mechanisms down to a minimum.
—James Hamilton

To model reality, we need to rely on Eventual Consistency. But don’t
be surprised: it’s how the world works. Again, we should not fight
reality; we should embrace it! It makes life easier.
The term ACID 2.0 was coined7 by Pat Helland and is a summary of
a set of principles for eventually consistent protocol design. The
acronym is meant to somewhat challenge the traditional ACID from
database systems:
• The “A” in the acronym stands for Associative, which means that
grouping of messages does not matter and allows for batching.
• The “C” is for Commutative, which means that ordering of mes‐
sages does not matter.
• The “I” stands for Idempotent, which means that duplication of
messages does not matter.
• The “D” could stand for Distributed, but is probably included

just to make the ACID acronym work.
There has been a lot of buzz about eventual consistency, and for
good reason. It allows us to raise the ceiling on what can be done in
terms of scalability, availability, and reduced coupling.
However, relying on eventual consistency is sometimes not permis‐
sible, because it can force us to give up too much of the high-level
business semantics. If this is the case, using causal consistency can be
a good trade-off. Semantics based on causality is what humans
expect and find intuitive. The good news is that causal consistency
can be made both scalable and available (and is even proven8 to be
the best we can do in an always available system).

7 Another excellent paper by Pat Helland, in which he introduced the idea of ACID 2.0,

in “Building on Quicksand.”

8 That causal consistency is the strongest consistency that we can achieve in an always

available system was proved by Mahajan et al. in their influential paper “Consistency,
Availability, and Convergence”.

14

|

Chapter 3: Microservices Come in Systems


Causal consistency is usually implemented using logical time instead
of synchronized clocks. The use of wall-clock time (timestamps) for

state coordination is something that should most often be avoided
in distributed system design due to the problems of coordinating
clocks across nodes, clock skew, and so on. This is the reason why it
is often better to rely on logical time, which gives you a stable notion
of time that you can trust, even if nodes fail, messages drop, and so
forth. There are several good options available, such as vector clocks,9
or Conflict-Free Replicated Data Types (CRDTs).10
CRDTs is one of the most interesting ideas coming out of dis‐
tributed systems research in recent years, giving us rich, eventually
consistent, and composable data-structures—such as counters,
maps, and sets—that are guaranteed to converge consistently
without the need for explicit coordination. CRDTs don’t fit all use
cases, but is a very valuable tool11 when building scalable and avail‐
able systems of microservices.
Let’s now look at three powerful tools for moving beyond microliths
that can help you to manage the complexity of distributed systems
while taking advantage of its opportunities for scalability and resil‐
ience:
• Events-First Domain-Driven Design
• Reactive Programming and Reactive Systems
• Event-Based Persistence

9 For good discussions on vector clocks, see the articles “Why Vector Clocks Are Easy”

and “Why Vector Clocks Are Hard”.

10 For more information, see Mark Shapiro’s paper “A comprehensive study of Convergent

and Commutative Replicated Data Types”.


11 For a great production-grade library for CRDTs, see Akka Distributed Data.

Avoid Needless Consistency

|

15


×