SlideShare uma empresa Scribd logo
1 de 76
Baixar para ler offline
Resilience with Hystrix
An introduction

Uwe Friedrichsen, codecentric AG, 2014
@ufried
Uwe Friedrichsen | uwe.friedrichsen@codecentric.de | http://slideshare.net/ufried | http://ufried.tumblr.com
It‘s all about production!
Production
Availability
Resilience
Fault Tolerance
re•sil•ience (rɪˈzɪl yəns) also re•sil′ien•cy, n.

1.  the power or ability to return to the original form, position,
etc., after being bent, compressed, or stretched; elasticity.
2.  ability to recover readily from illness, depression, adversity,
or the like; buoyancy.

Random House Kernerman Webster's College Dictionary, © 2010 K Dictionaries Ltd.
Copyright 2005, 1997, 1991 by Random House, Inc. All rights reserved.


http://www.thefreedictionary.com/resilience
Implemented patterns






•  Timeout
•  Circuit breaker
•  Load shedder
Supported patterns

•  Bulkheads

(a.k.a. Failure Units)
•  Fail fast
•  Fail silently
•  Graceful degradation of service
•  Failover
•  Escalation
•  Retry
•  ...
That‘s been enough theory
Give us some code – now!
Basics
Hello, world!
public class HelloCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final String name;
public HelloCommand(String name) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.name = name;
}
@Override
protected String run() throws Exception {
return "Hello, " + name;
}
}
@Test
public void shouldGreetWorld() {
String result = new HelloCommand("World").execute();
assertEquals("Hello, World", result);
}
Synchronous execution
@Test
public void shouldExecuteSynchronously() {
String s = new HelloCommand("Bob").execute();
assertEquals("Hello, Bob", s);
}
Asynchronous execution
@Test
public void shouldExecuteAsynchronously() {
Future<String> f = new HelloCommand("Alice").queue();
String s = null;
try {
s = f.get();
} catch (InterruptedException | ExecutionException e) {
// Handle Future.get() exceptions here
}
assertEquals("Hello, Alice", s);
}
Reactive execution (simple)
@Test
public void shouldExecuteReactiveSimple() {
Observable<String> observable =
new HelloCommand("Alice").observe();
String s = observable.toBlockingObservable().single();
assertEquals("Hello, Alice", s);
}
Reactive execution (full)
public class CommandHelloObserver implements Observer<String> {
// Needed for communication between observer and test case
private final AtomicReference<String> aRef;
private final Semaphore semaphore;
public CommandHelloObserver(AtomicReference<String> aRef,
Semaphore semaphore) {
this.aRef = aRef;
this.semaphore = semaphore;
}
@Override
public void onCompleted() { // Not needed here }
@Override
public void onError(Throwable e) { // Not needed here }
@Override
public void onNext(String s) {
aRef.set(s);
semaphore.release();
}
}
@Test
public void shouldExecuteReactiveFull() {
// Needed for communication between observer and test case
AtomicReference<String> aRef = new AtomicReference<>();
Semaphore semaphore = new Semaphore(0);
Observable<String> observable = new HelloCommand("Bob").observe();
Observer<String> observer =
new CommandHelloObserver(aRef, semaphore);
observable.subscribe(observer);
// Wait until observer received a result
try {
semaphore.acquire();
} catch (InterruptedException e) {
// Handle exceptions here
}
String s = aRef.get();
assertEquals("Hello, Bob", s);
}
Fallback
public class FallbackCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
public FallbackCommand() {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
}
@Override
protected String run() throws Exception {
throw new RuntimeException("I will always fail");
}
@Override
protected String getFallback() {
return "Powered by fallback";
}
}
@Test
public void shouldGetFallbackResponse() {
String s = new FallbackCommand().execute();
assertEquals("Powered by fallback", s);
}
Error propagation
public class ErrorPropagationCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
protected ErrorPropagationCommand() {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
}
@Override
protected String run() throws Exception {
throw new HystrixBadRequestException("I fail differently",
new RuntimeException("I will always fail"));
}
@Override
protected String getFallback() {
return "Powered by fallback";
}
}
@Test
public void shouldGetFallbackResponse() {
String s = null;
try {
s = new ErrorPropagationCommand().execute();
fail(); // Just to make sure
} catch (HystrixBadRequestException e) {
assertEquals("I will fail differently", e.getMessage());
assertEquals("I will always fail", e.getCause().getMessage());
}
assertNull(s); // Fallback is not triggered
}
How it works
Source: https://github.com/Netflix/Hystrix/wiki/How-it-Works
Source: https://github.com/Netflix/Hystrix/wiki/How-it-Works
Source: https://github.com/Netflix/Hystrix/wiki/How-it-Works
Configuration
Configuration

•  Based on Archaius by Netflix

which extends the Apache Commons Configuration Library
•  Four levels of precedence
1.  Global default from code
2.  Dynamic global default property
3.  Instance default from code
4.  Dynamic instance property

Provides many configuration & tuning options
Configuration – Some examples

•  hystrix.command.*.execution.isolation.strategy

Either “THREAD” or “SEMAPHORE” (Default: THREAD)
•  hystrix.command.*.execution.isolation.thread.timeoutInMilliseconds

Time to wait for the run() method to complete (Default: 1000)
•  h.c.*.execution.isolation.semaphore.maxConcurrentRequests

Maximum number of concurrent requests when using semaphores (Default: 10)
•  hystrix.command.*.circuitBreaker.errorThresholdPercentage

Error percentage at which the breaker trips open (Default: 50)
•  hystrix.command.*.circuitBreaker.sleepWindowInMilliseconds

Time to wait before attempting to reset the breaker after tripping (Default: 5000)
* must be either “default” or the command key name
Configuration – More examples

•  hystrix.threadpool.*.coreSize

Maximum number of concurrent requests when using thread pools (Default: 10)
•  hystrix.threadpool.*.maxQueueSize

Maximum LinkedBlockingQueue size - -1 for using SynchronousQueue (Default: -1)
•  hystrix.threadpool.default.queueSizeRejectionThreshold

Queue size rejection threshold (Default: 5)

… and many more
* must be either “default” or the thread pool key name
Patterns
Timeout
public class LatentResource {
private final long latency;
public LatentResource(long latency) {
this.latency = ((latency < 0L) ? 0L : latency);
}
public String getData() {
addLatency(); // This is the important part
return "Some value”;
}
private void addLatency() {
try {
Thread.sleep(latency);
} catch (InterruptedException e) {
// We do not care about this here
}
}
}
public class TimeoutCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final LatentResource resource;
public TimeoutCommand(int timeout, LatentResource resource) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationThreadTimeoutInMilliseconds(
timeout)));
this.resource = resource;
}
@Override
protected String run() throws Exception {
return resource.getData();
}
@Override
protected String getFallback() {
return "Resource timed out";
}
}
@Test
public void shouldGetNormalResponse() {
LatentResource resource = new LatentResource(50L);
TimeoutCommand command = new TimeoutCommand(100, resource);
String s = command.execute();
assertEquals("Some value", s);
}
@Test
public void shouldGetFallbackResponse() {
LatentResource resource = new LatentResource(150L);
TimeoutCommand command = new TimeoutCommand(100, resource);
String s = command.execute();
assertEquals("Resource timed out", s);
}
Circuit breaker
public class CircuitBreakerCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
public CircuitBreakerCommand(String name, boolean open) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP))
.andCommandKey(HystrixCommandKey.Factory.asKey(name))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerForceOpen(open)));
}
@Override
protected String run() throws Exception {
return("Some result");
}
@Override
protected String getFallback() {
return "Fallback response";
}
}
@Test
public void shouldExecuteWithCircuitBreakerClosed() {
CircuitBreakerCommand command = new
CircuitBreakerCommand("ClosedBreaker", false);
String s = command.execute();
assertEquals("Some result", s);
HystrixCircuitBreaker breaker =
HystrixCircuitBreaker.Factory
.getInstance(command.getCommandKey());
assertTrue(breaker.allowRequest());
}
@Test
public void shouldExecuteWithCircuitBreakerOpen() {
CircuitBreakerCommand command = new
CircuitBreakerCommand("OpenBreaker", true);
String s = command.execute();
assertEquals("Fallback response", s);
HystrixCircuitBreaker breaker =
HystrixCircuitBreaker.Factory
.getInstance(command.getCommandKey());
assertFalse(breaker.allowRequest());
}
Load shedder (Thread pool)
public class LatentCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final LatentResource resource;
public LatentCommand(LatentResource resource) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.resource = resource;
}
@Override
protected String run() throws Exception {
return resource.getData();
}
@Override
protected String getFallback() {
return "Fallback triggered";
}
}
@Test
public void shouldShedLoad() {
List<Future<String>> l = new LinkedList<>();
LatentResource resource = new LatentResource(500L); // Make latent
// Use up all available threads
for (int i = 0; i < 10; i++)
l.add(new LatentCommand(resource).queue());
// Call will be rejected as thread pool is exhausted
String s = new LatentCommand(resource).execute();
assertEquals("Fallback triggered", s);
// All other calls succeed
for (Future<String> f : l)
assertEquals("Some value", get(f)); // wrapper for f.get()
}
Load shedder (Semaphore)
public class SemaphoreCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final LatentResource resource;
public SemaphoreCommand(LatentResource resource) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties
.ExecutionIsolationStrategy.SEMAPHORE)));
this.resource = resource;
}
@Override
protected String run() throws Exception {
return resource.getData();
}
@Override
protected String getFallback() {
return "Fallback triggered";
}
}
private class SemaphoreCommandInvocation implements
java.util.concurrent.Callable<String> {
private final LatentResource resource;
public SemaphoreCommandInvocation(LatentResource resource) {
this.resource = resource;
}
@Override
public String call() throws Exception {
return new SemaphoreCommand(resource).execute();
}
}
@Test
public void shouldShedLoad() {
List<Future<String>> l = new LinkedList<>();
LatentResource resource = new LatentResource(500L); // Make latent
ExecutorService e = Executors.newFixedThreadPool(10);
// Use up all available semaphores
for (int i = 0; i < 10; i++)
l.add(e.submit(new SemaphoreCommandInvocation(resource)));
// Wait a moment to make sure all commands are started
pause(250L); // Wrapper around Thread.sleep()
// Call will be rejected as all semaphores are in use
String s = new SemaphoreCommand(resource).execute();
assertEquals("Fallback triggered", s);
// All other calls succeed
for (Future<String> f : l)
assertEquals("Some value", get(f));
}
Fail fast
public class FailFastCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final boolean preCondition;
public FailFastCommand(boolean preCondition) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.preCondition = preCondition;
}
@Override
protected String run() throws Exception {
if (!preCondition)
throw new RuntimeException(("Fail fast"));
return "Some value";
}
}
@Test
public void shouldSucceed() {
FailFastCommand command = new FailFastCommand(true);
String s = command.execute();
assertEquals("Some value", s);
}
@Test
public void shouldFailFast() {
FailFastCommand command = new FailFastCommand(false);
try {
String s = command.execute();
fail("Did not fail fast");
} catch (Exception e) {
assertEquals(HystrixRuntimeException.class, e.getClass());
assertEquals("Fail fast", e.getCause().getMessage());
}
}
Fail silent
public class FailSilentCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final boolean preCondition;
public FailSilentCommand(boolean preCondition) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.preCondition = preCondition;
}
@Override
protected String run() throws Exception {
if (!preCondition)
throw new RuntimeException(("Fail fast"));
return "Some value";
}
@Override
protected String getFallback() {
return null; // Turn into silent failure
}
}
@Test
public void shouldSucceed() {
FailSilentCommand command = new FailSilentCommand(true);
String s = command.execute();
assertEquals("Some value", s);
}
@Test
public void shouldFailSilent() {
FailSilentCommand command = new FailSilentCommand(false);
String s = "Test value";
try {
s = command.execute();
} catch (Exception e) {
fail("Did not fail silent");
}
assertNull(s);
}
Static fallback
public class StaticFallbackCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "default";
private final boolean preCondition;
public StaticFallbackCommand(boolean preCondition) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.preCondition = preCondition;
}
@Override
protected String run() throws Exception {
if (!preCondition)
throw new RuntimeException(("Fail fast"));
return "Some value";
}
@Override
protected String getFallback() {
return "Some static fallback value"; // Static fallback
}
}
@Test
public void shouldSucceed() {
StaticFallbackCommand command = new StaticFallbackCommand(true);
String s = command.execute();
assertEquals("Some value", s);
}
@Test
public void shouldProvideStaticFallback() {
StaticFallbackCommand command = new StaticFallbackCommand(false);
String s = null;
try {
s = command.execute();
} catch (Exception e) {
fail("Did not fail silent");
}
assertEquals("Some static fallback value", s);
}
Cache fallback
public class CacheClient {
private final Map<String, String> map;
public CacheClient() {
map = new ConcurrentHashMap<>();
}
public void add(String key, String value) {
map.put(key, value);
}
public String get(String key) {
return map.get(key);
}
}
public class CacheCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "primary";
private final CacheClient cache;
private final boolean failPrimary;
private final String req;
public CacheCommand(CacheClient cache, boolean failPrimary,
String request) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.cache = cache;
this.failPrimary = failPrimary;
this.req = request;
}
@Override
protected String run() throws Exception {
if (failPrimary)
throw new RuntimeException("Failure of primary");
String s = req + "-o";
cache.add(req, s);
return(s);
}
…
…
@Override
protected String getFallback() {
return "Cached: " + new FBCommand(cache, req).execute();
}
private static class FBCommand extends HystrixCommand<String> {
private static final String COMMAND_GROUP = "fallback";
private final CacheClient cache;
private final String request;
public FBCommand(CacheClient cache, String request) {
super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP));
this.cache = cache;
this.request = request;
}
@Override
protected String run() throws Exception {
return cache.get(request);
}
}
}
@Test
public void shouldExecuteWithoutCache() {
CacheClient cache = new CacheClient();
String s = new CacheCommand(cache, false, "ping”).execute();
assertEquals("ping-o", s);
}
@Test
public void shouldRetrieveValueFromCache() {
CacheClient cache = new CacheClient();
new CacheCommand(cache, false, "ping").execute();
String s = new CacheCommand(cache, true, "ping”).execute();
assertEquals("Cached: ping-o", s);
}
@Test
public void shouldNotRetrieveAnyValue() {
CacheClient cache = new CacheClient();
String s = new CacheCommand(cache, true, "ping”).execute();
assertEquals("Cached: null", s);
}
… and so on
Advanced features
Advanced Features

•  Request Caching
•  Request Collapsing
•  Plugins

Event Notifier, Metrics Publisher, Properties Strategy,
Concurrency Strategy, Command Execution Hook
•  Contributions

Metrics Event Stream, Metrics Publisher, Java Annotations,
Clojure Bindings, …
•  Dashboard
Source: https://github.com/Netflix/Hystrix/wiki/Dashboard
Source: https://github.com/Netflix/Hystrix/wiki/Dashboard
Further reading

1.  Hystrix Wiki,

https://github.com/Netflix/Hystrix/wiki
2.  Michael T. Nygard, Release It!,
Pragmatic Bookshelf, 2007
3.  Robert S. Hanmer,

Patterns for Fault Tolerant Software,
Wiley, 2007
4.  Andrew Tanenbaum, Marten van Steen,
Distributed Systems – Principles and
Paradigms,

Prentice Hall, 2nd Edition, 2006
Wrap-up

•  Resilient software design becomes a must
•  Hystrix is a resilience library
•  Provides isolation and latency control
•  Easy to start with
•  Yet some learning curve
•  Many fault-tolerance patterns implementable
•  Many configuration options
•  Customizable
•  Operations proven

Become a resilient software developer!
It‘s all about production!
@ufried
Uwe Friedrichsen | uwe.friedrichsen@codecentric.de | http://slideshare.net/ufried | http://ufried.tumblr.com
Resilience with Hystrix

Mais conteúdo relacionado

Mais procurados (6)

We Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End DevelopmentWe Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End Development
 
Prometheus (Monitorama 2016)
Prometheus (Monitorama 2016)Prometheus (Monitorama 2016)
Prometheus (Monitorama 2016)
 
Terraform - Taming Modern Clouds
Terraform  - Taming Modern CloudsTerraform  - Taming Modern Clouds
Terraform - Taming Modern Clouds
 
Terraform modules restructured
Terraform modules restructuredTerraform modules restructured
Terraform modules restructured
 
The Need for Async @ ScalaWorld
The Need for Async @ ScalaWorldThe Need for Async @ ScalaWorld
The Need for Async @ ScalaWorld
 
Atmosphere whitepaper
Atmosphere whitepaperAtmosphere whitepaper
Atmosphere whitepaper
 

Semelhante a Resilience with Hystrix

Semelhante a Resilience with Hystrix (20)

Resilience mit Hystrix
Resilience mit HystrixResilience mit Hystrix
Resilience mit Hystrix
 
Resilence patterns kr
Resilence patterns krResilence patterns kr
Resilence patterns kr
 
Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]
 
Java APIs- The missing manual (concurrency)
Java APIs- The missing manual (concurrency)Java APIs- The missing manual (concurrency)
Java APIs- The missing manual (concurrency)
 
JavaOne 2017 - TestContainers: integration testing without the hassle
JavaOne 2017 - TestContainers: integration testing without the hassleJavaOne 2017 - TestContainers: integration testing without the hassle
JavaOne 2017 - TestContainers: integration testing without the hassle
 
Testing Java Code Effectively
Testing Java Code EffectivelyTesting Java Code Effectively
Testing Java Code Effectively
 
Testing basics for developers
Testing basics for developersTesting basics for developers
Testing basics for developers
 
Java util concurrent
Java util concurrentJava util concurrent
Java util concurrent
 
Fork and join framework
Fork and join frameworkFork and join framework
Fork and join framework
 
Architecting for Microservices Part 2
Architecting for Microservices Part 2Architecting for Microservices Part 2
Architecting for Microservices Part 2
 
Curator intro
Curator introCurator intro
Curator intro
 
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
 
Cassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra ApplicationsCassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra Applications
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit Testing
 
Java design patterns
Java design patternsJava design patterns
Java design patterns
 
Java Concurrency, Memory Model, and Trends
Java Concurrency, Memory Model, and TrendsJava Concurrency, Memory Model, and Trends
Java Concurrency, Memory Model, and Trends
 
JRuby and Invokedynamic - Japan JUG 2015
JRuby and Invokedynamic - Japan JUG 2015JRuby and Invokedynamic - Japan JUG 2015
JRuby and Invokedynamic - Japan JUG 2015
 
Introduction of failsafe
Introduction of failsafeIntroduction of failsafe
Introduction of failsafe
 
MicroProfile: A Quest for a Lightweight and Modern Enterprise Java Platform
MicroProfile: A Quest for a Lightweight and Modern Enterprise Java PlatformMicroProfile: A Quest for a Lightweight and Modern Enterprise Java Platform
MicroProfile: A Quest for a Lightweight and Modern Enterprise Java Platform
 

Mais de Uwe Friedrichsen

Timeless design in a cloud-native world
Timeless design in a cloud-native worldTimeless design in a cloud-native world
Timeless design in a cloud-native world
Uwe Friedrichsen
 
Deep learning - a primer
Deep learning - a primerDeep learning - a primer
Deep learning - a primer
Uwe Friedrichsen
 
Real-world consistency explained
Real-world consistency explainedReal-world consistency explained
Real-world consistency explained
Uwe Friedrichsen
 
The 7 quests of resilient software design
The 7 quests of resilient software designThe 7 quests of resilient software design
The 7 quests of resilient software design
Uwe Friedrichsen
 
Excavating the knowledge of our ancestors
Excavating the knowledge of our ancestorsExcavating the knowledge of our ancestors
Excavating the knowledge of our ancestors
Uwe Friedrichsen
 
The truth about "You build it, you run it!"
The truth about "You build it, you run it!"The truth about "You build it, you run it!"
The truth about "You build it, you run it!"
Uwe Friedrichsen
 
The promises and perils of microservices
The promises and perils of microservicesThe promises and perils of microservices
The promises and perils of microservices
Uwe Friedrichsen
 
Resilient Functional Service Design
Resilient Functional Service DesignResilient Functional Service Design
Resilient Functional Service Design
Uwe Friedrichsen
 
DevOps is not enough - Embedding DevOps in a broader context
DevOps is not enough - Embedding DevOps in a broader contextDevOps is not enough - Embedding DevOps in a broader context
DevOps is not enough - Embedding DevOps in a broader context
Uwe Friedrichsen
 

Mais de Uwe Friedrichsen (20)

Timeless design in a cloud-native world
Timeless design in a cloud-native worldTimeless design in a cloud-native world
Timeless design in a cloud-native world
 
Deep learning - a primer
Deep learning - a primerDeep learning - a primer
Deep learning - a primer
 
Life after microservices
Life after microservicesLife after microservices
Life after microservices
 
The hitchhiker's guide for the confused developer
The hitchhiker's guide for the confused developerThe hitchhiker's guide for the confused developer
The hitchhiker's guide for the confused developer
 
Digitization solutions - A new breed of software
Digitization solutions - A new breed of softwareDigitization solutions - A new breed of software
Digitization solutions - A new breed of software
 
Real-world consistency explained
Real-world consistency explainedReal-world consistency explained
Real-world consistency explained
 
The 7 quests of resilient software design
The 7 quests of resilient software designThe 7 quests of resilient software design
The 7 quests of resilient software design
 
Excavating the knowledge of our ancestors
Excavating the knowledge of our ancestorsExcavating the knowledge of our ancestors
Excavating the knowledge of our ancestors
 
The truth about "You build it, you run it!"
The truth about "You build it, you run it!"The truth about "You build it, you run it!"
The truth about "You build it, you run it!"
 
The promises and perils of microservices
The promises and perils of microservicesThe promises and perils of microservices
The promises and perils of microservices
 
Resilient Functional Service Design
Resilient Functional Service DesignResilient Functional Service Design
Resilient Functional Service Design
 
Watch your communication
Watch your communicationWatch your communication
Watch your communication
 
Life, IT and everything
Life, IT and everythingLife, IT and everything
Life, IT and everything
 
Resilience reloaded - more resilience patterns
Resilience reloaded - more resilience patternsResilience reloaded - more resilience patterns
Resilience reloaded - more resilience patterns
 
DevOps is not enough - Embedding DevOps in a broader context
DevOps is not enough - Embedding DevOps in a broader contextDevOps is not enough - Embedding DevOps in a broader context
DevOps is not enough - Embedding DevOps in a broader context
 
Production-ready Software
Production-ready SoftwareProduction-ready Software
Production-ready Software
 
Towards complex adaptive architectures
Towards complex adaptive architecturesTowards complex adaptive architectures
Towards complex adaptive architectures
 
Conway's law revisited - Architectures for an effective IT
Conway's law revisited - Architectures for an effective ITConway's law revisited - Architectures for an effective IT
Conway's law revisited - Architectures for an effective IT
 
Microservices - stress-free and without increased heart attack risk
Microservices - stress-free and without increased heart attack riskMicroservices - stress-free and without increased heart attack risk
Microservices - stress-free and without increased heart attack risk
 
Patterns of resilience
Patterns of resiliencePatterns of resilience
Patterns of resilience
 

Último

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Último (20)

Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
HTML Injection Attacks: Impact and Mitigation Strategies
HTML Injection Attacks: Impact and Mitigation StrategiesHTML Injection Attacks: Impact and Mitigation Strategies
HTML Injection Attacks: Impact and Mitigation Strategies
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 

Resilience with Hystrix

  • 1. Resilience with Hystrix An introduction Uwe Friedrichsen, codecentric AG, 2014
  • 2. @ufried Uwe Friedrichsen | uwe.friedrichsen@codecentric.de | http://slideshare.net/ufried | http://ufried.tumblr.com
  • 3. It‘s all about production!
  • 5. re•sil•ience (rɪˈzɪl yəns) also re•sil′ien•cy, n. 1.  the power or ability to return to the original form, position, etc., after being bent, compressed, or stretched; elasticity. 2.  ability to recover readily from illness, depression, adversity, or the like; buoyancy. Random House Kernerman Webster's College Dictionary, © 2010 K Dictionaries Ltd. Copyright 2005, 1997, 1991 by Random House, Inc. All rights reserved. http://www.thefreedictionary.com/resilience
  • 6.
  • 7. Implemented patterns •  Timeout •  Circuit breaker •  Load shedder
  • 8. Supported patterns •  Bulkheads
 (a.k.a. Failure Units) •  Fail fast •  Fail silently •  Graceful degradation of service •  Failover •  Escalation •  Retry •  ...
  • 9. That‘s been enough theory Give us some code – now!
  • 12. public class HelloCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final String name; public HelloCommand(String name) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.name = name; } @Override protected String run() throws Exception { return "Hello, " + name; } }
  • 13. @Test public void shouldGreetWorld() { String result = new HelloCommand("World").execute(); assertEquals("Hello, World", result); }
  • 15. @Test public void shouldExecuteSynchronously() { String s = new HelloCommand("Bob").execute(); assertEquals("Hello, Bob", s); }
  • 17. @Test public void shouldExecuteAsynchronously() { Future<String> f = new HelloCommand("Alice").queue(); String s = null; try { s = f.get(); } catch (InterruptedException | ExecutionException e) { // Handle Future.get() exceptions here } assertEquals("Hello, Alice", s); }
  • 19. @Test public void shouldExecuteReactiveSimple() { Observable<String> observable = new HelloCommand("Alice").observe(); String s = observable.toBlockingObservable().single(); assertEquals("Hello, Alice", s); }
  • 21. public class CommandHelloObserver implements Observer<String> { // Needed for communication between observer and test case private final AtomicReference<String> aRef; private final Semaphore semaphore; public CommandHelloObserver(AtomicReference<String> aRef, Semaphore semaphore) { this.aRef = aRef; this.semaphore = semaphore; } @Override public void onCompleted() { // Not needed here } @Override public void onError(Throwable e) { // Not needed here } @Override public void onNext(String s) { aRef.set(s); semaphore.release(); } }
  • 22. @Test public void shouldExecuteReactiveFull() { // Needed for communication between observer and test case AtomicReference<String> aRef = new AtomicReference<>(); Semaphore semaphore = new Semaphore(0); Observable<String> observable = new HelloCommand("Bob").observe(); Observer<String> observer = new CommandHelloObserver(aRef, semaphore); observable.subscribe(observer); // Wait until observer received a result try { semaphore.acquire(); } catch (InterruptedException e) { // Handle exceptions here } String s = aRef.get(); assertEquals("Hello, Bob", s); }
  • 24. public class FallbackCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; public FallbackCommand() { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); } @Override protected String run() throws Exception { throw new RuntimeException("I will always fail"); } @Override protected String getFallback() { return "Powered by fallback"; } }
  • 25. @Test public void shouldGetFallbackResponse() { String s = new FallbackCommand().execute(); assertEquals("Powered by fallback", s); }
  • 27. public class ErrorPropagationCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; protected ErrorPropagationCommand() { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); } @Override protected String run() throws Exception { throw new HystrixBadRequestException("I fail differently", new RuntimeException("I will always fail")); } @Override protected String getFallback() { return "Powered by fallback"; } }
  • 28. @Test public void shouldGetFallbackResponse() { String s = null; try { s = new ErrorPropagationCommand().execute(); fail(); // Just to make sure } catch (HystrixBadRequestException e) { assertEquals("I will fail differently", e.getMessage()); assertEquals("I will always fail", e.getCause().getMessage()); } assertNull(s); // Fallback is not triggered }
  • 34. Configuration •  Based on Archaius by Netflix
 which extends the Apache Commons Configuration Library •  Four levels of precedence 1.  Global default from code 2.  Dynamic global default property 3.  Instance default from code 4.  Dynamic instance property Provides many configuration & tuning options
  • 35. Configuration – Some examples •  hystrix.command.*.execution.isolation.strategy
 Either “THREAD” or “SEMAPHORE” (Default: THREAD) •  hystrix.command.*.execution.isolation.thread.timeoutInMilliseconds
 Time to wait for the run() method to complete (Default: 1000) •  h.c.*.execution.isolation.semaphore.maxConcurrentRequests
 Maximum number of concurrent requests when using semaphores (Default: 10) •  hystrix.command.*.circuitBreaker.errorThresholdPercentage
 Error percentage at which the breaker trips open (Default: 50) •  hystrix.command.*.circuitBreaker.sleepWindowInMilliseconds
 Time to wait before attempting to reset the breaker after tripping (Default: 5000) * must be either “default” or the command key name
  • 36. Configuration – More examples •  hystrix.threadpool.*.coreSize
 Maximum number of concurrent requests when using thread pools (Default: 10) •  hystrix.threadpool.*.maxQueueSize
 Maximum LinkedBlockingQueue size - -1 for using SynchronousQueue (Default: -1) •  hystrix.threadpool.default.queueSizeRejectionThreshold
 Queue size rejection threshold (Default: 5) … and many more * must be either “default” or the thread pool key name
  • 39. public class LatentResource { private final long latency; public LatentResource(long latency) { this.latency = ((latency < 0L) ? 0L : latency); } public String getData() { addLatency(); // This is the important part return "Some value”; } private void addLatency() { try { Thread.sleep(latency); } catch (InterruptedException e) { // We do not care about this here } } }
  • 40. public class TimeoutCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final LatentResource resource; public TimeoutCommand(int timeout, LatentResource resource) { super(Setter.withGroupKey( HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withExecutionIsolationThreadTimeoutInMilliseconds( timeout))); this.resource = resource; } @Override protected String run() throws Exception { return resource.getData(); } @Override protected String getFallback() { return "Resource timed out"; } }
  • 41. @Test public void shouldGetNormalResponse() { LatentResource resource = new LatentResource(50L); TimeoutCommand command = new TimeoutCommand(100, resource); String s = command.execute(); assertEquals("Some value", s); } @Test public void shouldGetFallbackResponse() { LatentResource resource = new LatentResource(150L); TimeoutCommand command = new TimeoutCommand(100, resource); String s = command.execute(); assertEquals("Resource timed out", s); }
  • 43. public class CircuitBreakerCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; public CircuitBreakerCommand(String name, boolean open) { super(Setter.withGroupKey( HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)) .andCommandKey(HystrixCommandKey.Factory.asKey(name)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withCircuitBreakerForceOpen(open))); } @Override protected String run() throws Exception { return("Some result"); } @Override protected String getFallback() { return "Fallback response"; } }
  • 44. @Test public void shouldExecuteWithCircuitBreakerClosed() { CircuitBreakerCommand command = new CircuitBreakerCommand("ClosedBreaker", false); String s = command.execute(); assertEquals("Some result", s); HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory .getInstance(command.getCommandKey()); assertTrue(breaker.allowRequest()); }
  • 45. @Test public void shouldExecuteWithCircuitBreakerOpen() { CircuitBreakerCommand command = new CircuitBreakerCommand("OpenBreaker", true); String s = command.execute(); assertEquals("Fallback response", s); HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory .getInstance(command.getCommandKey()); assertFalse(breaker.allowRequest()); }
  • 47. public class LatentCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final LatentResource resource; public LatentCommand(LatentResource resource) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.resource = resource; } @Override protected String run() throws Exception { return resource.getData(); } @Override protected String getFallback() { return "Fallback triggered"; } }
  • 48. @Test public void shouldShedLoad() { List<Future<String>> l = new LinkedList<>(); LatentResource resource = new LatentResource(500L); // Make latent // Use up all available threads for (int i = 0; i < 10; i++) l.add(new LatentCommand(resource).queue()); // Call will be rejected as thread pool is exhausted String s = new LatentCommand(resource).execute(); assertEquals("Fallback triggered", s); // All other calls succeed for (Future<String> f : l) assertEquals("Some value", get(f)); // wrapper for f.get() }
  • 50. public class SemaphoreCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final LatentResource resource; public SemaphoreCommand(LatentResource resource) { super(Setter.withGroupKey( HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(HystrixCommandProperties .ExecutionIsolationStrategy.SEMAPHORE))); this.resource = resource; } @Override protected String run() throws Exception { return resource.getData(); } @Override protected String getFallback() { return "Fallback triggered"; } }
  • 51. private class SemaphoreCommandInvocation implements java.util.concurrent.Callable<String> { private final LatentResource resource; public SemaphoreCommandInvocation(LatentResource resource) { this.resource = resource; } @Override public String call() throws Exception { return new SemaphoreCommand(resource).execute(); } }
  • 52. @Test public void shouldShedLoad() { List<Future<String>> l = new LinkedList<>(); LatentResource resource = new LatentResource(500L); // Make latent ExecutorService e = Executors.newFixedThreadPool(10); // Use up all available semaphores for (int i = 0; i < 10; i++) l.add(e.submit(new SemaphoreCommandInvocation(resource))); // Wait a moment to make sure all commands are started pause(250L); // Wrapper around Thread.sleep() // Call will be rejected as all semaphores are in use String s = new SemaphoreCommand(resource).execute(); assertEquals("Fallback triggered", s); // All other calls succeed for (Future<String> f : l) assertEquals("Some value", get(f)); }
  • 54. public class FailFastCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final boolean preCondition; public FailFastCommand(boolean preCondition) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.preCondition = preCondition; } @Override protected String run() throws Exception { if (!preCondition) throw new RuntimeException(("Fail fast")); return "Some value"; } }
  • 55. @Test public void shouldSucceed() { FailFastCommand command = new FailFastCommand(true); String s = command.execute(); assertEquals("Some value", s); } @Test public void shouldFailFast() { FailFastCommand command = new FailFastCommand(false); try { String s = command.execute(); fail("Did not fail fast"); } catch (Exception e) { assertEquals(HystrixRuntimeException.class, e.getClass()); assertEquals("Fail fast", e.getCause().getMessage()); } }
  • 57. public class FailSilentCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final boolean preCondition; public FailSilentCommand(boolean preCondition) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.preCondition = preCondition; } @Override protected String run() throws Exception { if (!preCondition) throw new RuntimeException(("Fail fast")); return "Some value"; } @Override protected String getFallback() { return null; // Turn into silent failure } }
  • 58. @Test public void shouldSucceed() { FailSilentCommand command = new FailSilentCommand(true); String s = command.execute(); assertEquals("Some value", s); } @Test public void shouldFailSilent() { FailSilentCommand command = new FailSilentCommand(false); String s = "Test value"; try { s = command.execute(); } catch (Exception e) { fail("Did not fail silent"); } assertNull(s); }
  • 60. public class StaticFallbackCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "default"; private final boolean preCondition; public StaticFallbackCommand(boolean preCondition) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.preCondition = preCondition; } @Override protected String run() throws Exception { if (!preCondition) throw new RuntimeException(("Fail fast")); return "Some value"; } @Override protected String getFallback() { return "Some static fallback value"; // Static fallback } }
  • 61. @Test public void shouldSucceed() { StaticFallbackCommand command = new StaticFallbackCommand(true); String s = command.execute(); assertEquals("Some value", s); } @Test public void shouldProvideStaticFallback() { StaticFallbackCommand command = new StaticFallbackCommand(false); String s = null; try { s = command.execute(); } catch (Exception e) { fail("Did not fail silent"); } assertEquals("Some static fallback value", s); }
  • 63. public class CacheClient { private final Map<String, String> map; public CacheClient() { map = new ConcurrentHashMap<>(); } public void add(String key, String value) { map.put(key, value); } public String get(String key) { return map.get(key); } }
  • 64. public class CacheCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "primary"; private final CacheClient cache; private final boolean failPrimary; private final String req; public CacheCommand(CacheClient cache, boolean failPrimary, String request) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.cache = cache; this.failPrimary = failPrimary; this.req = request; } @Override protected String run() throws Exception { if (failPrimary) throw new RuntimeException("Failure of primary"); String s = req + "-o"; cache.add(req, s); return(s); } …
  • 65. … @Override protected String getFallback() { return "Cached: " + new FBCommand(cache, req).execute(); } private static class FBCommand extends HystrixCommand<String> { private static final String COMMAND_GROUP = "fallback"; private final CacheClient cache; private final String request; public FBCommand(CacheClient cache, String request) { super(HystrixCommandGroupKey.Factory.asKey(COMMAND_GROUP)); this.cache = cache; this.request = request; } @Override protected String run() throws Exception { return cache.get(request); } } }
  • 66. @Test public void shouldExecuteWithoutCache() { CacheClient cache = new CacheClient(); String s = new CacheCommand(cache, false, "ping”).execute(); assertEquals("ping-o", s); } @Test public void shouldRetrieveValueFromCache() { CacheClient cache = new CacheClient(); new CacheCommand(cache, false, "ping").execute(); String s = new CacheCommand(cache, true, "ping”).execute(); assertEquals("Cached: ping-o", s); } @Test public void shouldNotRetrieveAnyValue() { CacheClient cache = new CacheClient(); String s = new CacheCommand(cache, true, "ping”).execute(); assertEquals("Cached: null", s); }
  • 69. Advanced Features •  Request Caching •  Request Collapsing •  Plugins
 Event Notifier, Metrics Publisher, Properties Strategy, Concurrency Strategy, Command Execution Hook •  Contributions
 Metrics Event Stream, Metrics Publisher, Java Annotations, Clojure Bindings, … •  Dashboard
  • 72. Further reading 1.  Hystrix Wiki,
 https://github.com/Netflix/Hystrix/wiki 2.  Michael T. Nygard, Release It!, Pragmatic Bookshelf, 2007 3.  Robert S. Hanmer,
 Patterns for Fault Tolerant Software, Wiley, 2007 4.  Andrew Tanenbaum, Marten van Steen, Distributed Systems – Principles and Paradigms,
 Prentice Hall, 2nd Edition, 2006
  • 73. Wrap-up •  Resilient software design becomes a must •  Hystrix is a resilience library •  Provides isolation and latency control •  Easy to start with •  Yet some learning curve •  Many fault-tolerance patterns implementable •  Many configuration options •  Customizable •  Operations proven Become a resilient software developer!
  • 74. It‘s all about production!
  • 75. @ufried Uwe Friedrichsen | uwe.friedrichsen@codecentric.de | http://slideshare.net/ufried | http://ufried.tumblr.com