|
| 1 | +/** |
| 2 | + * Copyright 2012 Netflix, Inc. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package com.netflix.hystrix.examples.demo; |
| 17 | + |
| 18 | +import java.math.BigDecimal; |
| 19 | +import java.net.HttpCookie; |
| 20 | +import java.util.Random; |
| 21 | + |
| 22 | +import com.netflix.config.ConfigurationManager; |
| 23 | +import com.netflix.hystrix.HystrixCommandKey; |
| 24 | +import com.netflix.hystrix.HystrixCommandMetrics; |
| 25 | +import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts; |
| 26 | +import com.netflix.hystrix.HystrixRequestLog; |
| 27 | +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; |
| 28 | +import rx.Observable; |
| 29 | +import rx.functions.Action0; |
| 30 | +import rx.functions.Action1; |
| 31 | +import rx.functions.Func1; |
| 32 | +import rx.functions.Func2; |
| 33 | + |
| 34 | +/** |
| 35 | + * Executable client that demonstrates the lifecycle, metrics, request log and behavior of HystrixCommands. |
| 36 | + */ |
| 37 | +public class HystrixCommandAsyncDemo { |
| 38 | + |
| 39 | +// public static void main(String args[]) { |
| 40 | +// new HystrixCommandAsyncDemo().startDemo(true); |
| 41 | +// } |
| 42 | + |
| 43 | + public HystrixCommandAsyncDemo() { |
| 44 | + /* |
| 45 | + * Instead of using injected properties we'll set them via Archaius |
| 46 | + * so the rest of the code behaves as it would in a real system |
| 47 | + * where it picks up properties externally provided. |
| 48 | + */ |
| 49 | + ConfigurationManager.getConfigInstance().setProperty("hystrix.threadpool.default.coreSize", 8); |
| 50 | + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.CreditCardCommand.execution.isolation.thread.timeoutInMilliseconds", 3000); |
| 51 | + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.GetUserAccountCommand.execution.isolation.thread.timeoutInMilliseconds", 50); |
| 52 | + // set the rolling percentile more granular so we see data change every second rather than every 10 seconds as is the default |
| 53 | + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.metrics.rollingPercentile.numBuckets", 60); |
| 54 | + } |
| 55 | + |
| 56 | + public void startDemo(boolean shouldLog) { |
| 57 | + startMetricsMonitor(shouldLog); |
| 58 | + while (true) { |
| 59 | + Observable<CreditCardAuthorizationResult> o = runSimulatedRequestOnCallerThread(shouldLog); |
| 60 | + o.subscribe(); |
| 61 | + try { |
| 62 | + Thread.sleep(400); |
| 63 | + } catch (InterruptedException ex) { |
| 64 | + ex.printStackTrace(); |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + private final static Random r = new Random(); |
| 70 | + |
| 71 | + private Observable<CreditCardAuthorizationResult> runSimulatedRequestOnCallerThread(final boolean shouldLog) { |
| 72 | + final HystrixRequestContext context = HystrixRequestContext.initializeContext(); |
| 73 | + //System.out.println("Map at start : " + map.get() + " : " + Thread.currentThread().getName()); |
| 74 | + |
| 75 | + return observeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment() |
| 76 | + .doOnUnsubscribe(new Action0() { |
| 77 | + @Override |
| 78 | + public void call() { |
| 79 | + if (shouldLog) { |
| 80 | + System.out.println("Request => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); |
| 81 | + } |
| 82 | + context.shutdown(); |
| 83 | + } |
| 84 | + }) |
| 85 | + .doOnError(new Action1<Throwable>() { |
| 86 | + @Override |
| 87 | + public void call(Throwable throwable) { |
| 88 | + throwable.printStackTrace(); |
| 89 | + } |
| 90 | + }); |
| 91 | + } |
| 92 | + |
| 93 | + private class Pair<A, B> { |
| 94 | + private final A a; |
| 95 | + private final B b; |
| 96 | + |
| 97 | + Pair(A a, B b) { |
| 98 | + this.a = a; |
| 99 | + this.b = b; |
| 100 | + } |
| 101 | + |
| 102 | + A a() { |
| 103 | + return this.a; |
| 104 | + } |
| 105 | + |
| 106 | + B b() { |
| 107 | + return this.b; |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + public Observable<CreditCardAuthorizationResult> observeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment() { |
| 112 | + /* fetch user object with http cookies */ |
| 113 | + Observable<UserAccount> user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).observe(); |
| 114 | + |
| 115 | + /* fetch the payment information (asynchronously) for the user so the credit card payment can proceed */ |
| 116 | + Observable<PaymentInformation> paymentInformation = user.flatMap(new Func1<UserAccount, Observable<PaymentInformation>>() { |
| 117 | + @Override |
| 118 | + public Observable<PaymentInformation> call(UserAccount userAccount) { |
| 119 | + return new GetPaymentInformationCommand(userAccount).observe(); |
| 120 | + } |
| 121 | + }); |
| 122 | + /* fetch the order we're processing for the user */ |
| 123 | + int orderIdFromRequestArgument = 13579; |
| 124 | + final Observable<Order> previouslySavedOrder = new GetOrderCommand(orderIdFromRequestArgument).observe(); |
| 125 | + |
| 126 | + return Observable.zip(paymentInformation, previouslySavedOrder, new Func2<PaymentInformation, Order, Pair<PaymentInformation, Order>>() { |
| 127 | + @Override |
| 128 | + public Pair<PaymentInformation, Order> call(PaymentInformation paymentInformation, Order order) { |
| 129 | + return new Pair<PaymentInformation, Order>(paymentInformation, order); |
| 130 | + } |
| 131 | + }).flatMap(new Func1<Pair<PaymentInformation, Order>, Observable<CreditCardAuthorizationResult>>() { |
| 132 | + @Override |
| 133 | + public Observable<CreditCardAuthorizationResult> call(Pair<PaymentInformation, Order> pair) { |
| 134 | + return new CreditCardCommand(pair.b(), pair.a(), new BigDecimal(123.45)).observe(); |
| 135 | + } |
| 136 | + }); |
| 137 | + } |
| 138 | + |
| 139 | + public void startMetricsMonitor(final boolean shouldLog) { |
| 140 | + Thread t = new Thread(new Runnable() { |
| 141 | + |
| 142 | + @Override |
| 143 | + public void run() { |
| 144 | + while (true) { |
| 145 | + /** |
| 146 | + * Since this is a simple example and we know the exact HystrixCommandKeys we are interested in |
| 147 | + * we will retrieve the HystrixCommandMetrics objects directly. |
| 148 | + * |
| 149 | + * Typically you would instead retrieve metrics from where they are published which is by default |
| 150 | + * done using Servo: https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring |
| 151 | + */ |
| 152 | + |
| 153 | + // wait 5 seconds on each loop |
| 154 | + try { |
| 155 | + Thread.sleep(5000); |
| 156 | + } catch (Exception e) { |
| 157 | + // ignore |
| 158 | + } |
| 159 | + |
| 160 | + // we are using default names so can use class.getSimpleName() to derive the keys |
| 161 | + HystrixCommandMetrics creditCardMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(CreditCardCommand.class.getSimpleName())); |
| 162 | + HystrixCommandMetrics orderMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetOrderCommand.class.getSimpleName())); |
| 163 | + HystrixCommandMetrics userAccountMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetUserAccountCommand.class.getSimpleName())); |
| 164 | + HystrixCommandMetrics paymentInformationMetrics = HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(GetPaymentInformationCommand.class.getSimpleName())); |
| 165 | + |
| 166 | + if (shouldLog) { |
| 167 | + // print out metrics |
| 168 | + StringBuilder out = new StringBuilder(); |
| 169 | + out.append("\n"); |
| 170 | + out.append("#####################################################################################").append("\n"); |
| 171 | + out.append("# CreditCardCommand: " + getStatsStringFromMetrics(creditCardMetrics)).append("\n"); |
| 172 | + out.append("# GetOrderCommand: " + getStatsStringFromMetrics(orderMetrics)).append("\n"); |
| 173 | + out.append("# GetUserAccountCommand: " + getStatsStringFromMetrics(userAccountMetrics)).append("\n"); |
| 174 | + out.append("# GetPaymentInformationCommand: " + getStatsStringFromMetrics(paymentInformationMetrics)).append("\n"); |
| 175 | + out.append("#####################################################################################").append("\n"); |
| 176 | + System.out.println(out.toString()); |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + private String getStatsStringFromMetrics(HystrixCommandMetrics metrics) { |
| 182 | + StringBuilder m = new StringBuilder(); |
| 183 | + if (metrics != null) { |
| 184 | + HealthCounts health = metrics.getHealthCounts(); |
| 185 | + m.append("Requests: ").append(health.getTotalRequests()).append(" "); |
| 186 | + m.append("Errors: ").append(health.getErrorCount()).append(" (").append(health.getErrorPercentage()).append("%) "); |
| 187 | + m.append("Mean: ").append(metrics.getExecutionTimePercentile(50)).append(" "); |
| 188 | + m.append("75th: ").append(metrics.getExecutionTimePercentile(75)).append(" "); |
| 189 | + m.append("90th: ").append(metrics.getExecutionTimePercentile(90)).append(" "); |
| 190 | + m.append("99th: ").append(metrics.getExecutionTimePercentile(99)).append(" "); |
| 191 | + } |
| 192 | + return m.toString(); |
| 193 | + } |
| 194 | + |
| 195 | + }); |
| 196 | + t.setDaemon(true); |
| 197 | + t.start(); |
| 198 | + } |
| 199 | +} |
0 commit comments