Commit 3e907245 by Ryan Baxter

Switched branch in docs and merged changes forward

parents 4e8d5886 efb91874
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
:github-code: http://github.com/{github-repo}/tree/{github-tag} :github-code: http://github.com/{github-repo}/tree/{github-tag}
:all: {asterisk}{asterisk} :all: {asterisk}{asterisk}
:nofooter: :nofooter:
:imagesdir: ./images :branch: master
= Spring Cloud Netflix = Spring Cloud Netflix
*{spring-cloud-version}* *{spring-cloud-version}*
...@@ -528,12 +528,12 @@ example `eureka.instance.hostname=${HOST_NAME}`. ...@@ -528,12 +528,12 @@ example `eureka.instance.hostname=${HOST_NAME}`.
Netflix has created a library called https://github.com/Netflix/Hystrix[Hystrix] that implements the http://martinfowler.com/bliki/CircuitBreaker.html[circuit breaker pattern]. In a microservice architecture it is common to have multiple layers of service calls. Netflix has created a library called https://github.com/Netflix/Hystrix[Hystrix] that implements the http://martinfowler.com/bliki/CircuitBreaker.html[circuit breaker pattern]. In a microservice architecture it is common to have multiple layers of service calls.
.Microservice Graph .Microservice Graph
image::HystrixGraph.png[] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-netflix/{branch}/docs/src/main/asciidoc/images/Hystrix.png[]
A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service is greater than `circuitBreaker.requestVolumeThreshold` (default: 20 requests) and failue percentage is greater than `circuitBreaker.errorThresholdPercentage` (default: >50%) in a rolling window defined by `metrics.rollingStats.timeInMilliseconds` (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit a fallback can be provided by the developer. A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service is greater than `circuitBreaker.requestVolumeThreshold` (default: 20 requests) and failue percentage is greater than `circuitBreaker.errorThresholdPercentage` (default: >50%) in a rolling window defined by `metrics.rollingStats.timeInMilliseconds` (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit a fallback can be provided by the developer.
.Hystrix fallback prevents cascading failures .Hystrix fallback prevents cascading failures
image::HystrixFallback.png[] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-netflix/{branch}/docs/src/main/asciidoc/images/HystrixFallback.png[]
Having an open circuit stops cascading failures and allows overwhelmed or failing services time to heal. The fallback can be another Hystrix protected call, static data or a sane empty value. Fallbacks may be chained so the first fallback makes some other business call which in turn falls back to static data. Having an open circuit stops cascading failures and allows overwhelmed or failing services time to heal. The fallback can be another Hystrix protected call, static data or a sane empty value. Fallbacks may be chained so the first fallback makes some other business call which in turn falls back to static data.
...@@ -639,7 +639,7 @@ To enable the Hystrix metrics stream include a dependency on `spring-boot-starte ...@@ -639,7 +639,7 @@ To enable the Hystrix metrics stream include a dependency on `spring-boot-starte
One of the main benefits of Hystrix is the set of metrics it gathers about each HystrixCommand. The Hystrix Dashboard displays the health of each circuit breaker in an efficient manner. One of the main benefits of Hystrix is the set of metrics it gathers about each HystrixCommand. The Hystrix Dashboard displays the health of each circuit breaker in an efficient manner.
.Hystrix Dashboard .Hystrix Dashboard
image::Hystrix.png[] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-netflix/{branch}/docs/src/main/asciidoc/images/Hystrix.png[]
== Hystrix Timeouts And Ribbon Clients == Hystrix Timeouts And Ribbon Clients
...@@ -1926,7 +1926,7 @@ maps all paths in "/api/{all}" to the Zuul filter chain. ...@@ -1926,7 +1926,7 @@ maps all paths in "/api/{all}" to the Zuul filter chain.
=== Disable Zuul Filters === Disable Zuul Filters
Zuul for Spring Cloud comes with a number of `ZuulFilter` beans enabled by default Zuul for Spring Cloud comes with a number of `ZuulFilter` beans enabled by default
in both proxy and server mode. See https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters[the zuul filters package] for the in both proxy and server mode. See https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters[the zuul filters package] for the
possible filters that are enabled. If you want to disable one, simply set possible filters that are enabled. If you want to disable one, simply set
`zuul.<SimpleClassName>.<filterType>.disable=true`. By convention, the package after `zuul.<SimpleClassName>.<filterType>.disable=true`. By convention, the package after
`filters` is the Zuul filter type. For example to disable `filters` is the Zuul filter type. For example to disable
...@@ -2598,7 +2598,7 @@ The counter records a single time-normalized statistic. ...@@ -2598,7 +2598,7 @@ The counter records a single time-normalized statistic.
A timer is used to measure how long some event is taking. Spring Cloud automatically records timers for Spring MVC requests and conditionally `RestTemplate` requests, which can later be used to create dashboards for request related metrics like latency: A timer is used to measure how long some event is taking. Spring Cloud automatically records timers for Spring MVC requests and conditionally `RestTemplate` requests, which can later be used to create dashboards for request related metrics like latency:
.Request Latency .Request Latency
image::RequestLatency.png[] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-netflix/{branch}/docs/src/main/asciidoc/images/RequestLatency.png[]
[source,java] [source,java]
---- ----
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package org.springframework.cloud.netflix.hystrix.contract; package org.springframework.cloud.netflix.hystrix.contract;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
...@@ -22,10 +24,9 @@ import java.util.Map; ...@@ -22,10 +24,9 @@ import java.util.Map;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Dave Syer * @author Dave Syer
* @author Daniel Lavoie
* *
*/ */
public class HystrixContractUtils { public class HystrixContractUtils {
...@@ -41,6 +42,11 @@ public class HystrixContractUtils { ...@@ -41,6 +42,11 @@ public class HystrixContractUtils {
} }
} }
public static void checkEvent(String event) {
assertThat(event).isNotNull();
assertThat(event).isEqualTo("message");
}
public static void checkOrigin(Map<String, Object> origin) { public static void checkOrigin(Map<String, Object> origin) {
assertThat(origin.get("host")).isNotNull(); assertThat(origin.get("host")).isNotNull();
assertThat(origin.get("port")).isNotNull(); assertThat(origin.get("port")).isNotNull();
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
"serviceId":"application", "serviceId":"application",
"id":"application:0" "id":"application:0"
}, },
"event" : "message",
"data":{ "data":{
"type":"HystrixCommand", "type":"HystrixCommand",
"name":"application.hello", "name":"application.hello",
......
...@@ -138,6 +138,7 @@ public class HystrixStreamTask implements ApplicationContextAware { ...@@ -138,6 +138,7 @@ public class HystrixStreamTask implements ApplicationContextAware {
json.writeStartObject(); json.writeStartObject();
addServiceData(json, registration); addServiceData(json, registration);
json.writeStringField("event", "message");
json.writeObjectFieldStart("data"); json.writeObjectFieldStart("data");
json.writeStringField("type", "HystrixCommand"); json.writeStringField("type", "HystrixCommand");
String name = key.name(); String name = key.name();
......
...@@ -27,7 +27,6 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; ...@@ -27,7 +27,6 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.cloud.stream.test.binder.MessageCollector;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
...@@ -43,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -43,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Daniel Lavoie
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
...@@ -94,6 +94,8 @@ public class HystrixStreamTests { ...@@ -94,6 +94,8 @@ public class HystrixStreamTests {
JsonNode tree = mapper.readTree((String)message.getPayload()); JsonNode tree = mapper.readTree((String)message.getPayload());
assertThat(tree.hasNonNull("origin")); assertThat(tree.hasNonNull("origin"));
assertThat(tree.hasNonNull("data")); assertThat(tree.hasNonNull("data"));
assertThat(tree.hasNonNull("event"));
assertThat(tree.findValue("event").asText().equals("message"));
} }
} }
...@@ -87,6 +87,10 @@ public abstract class StreamSourceTestBase { ...@@ -87,6 +87,10 @@ public abstract class StreamSourceTestBase {
"application.hello"); "application.hello");
} }
public void assertEvent(Object input) {
HystrixContractUtils.checkEvent((String) input);
}
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableCircuitBreaker @EnableCircuitBreaker
@RestController @RestController
......
...@@ -22,6 +22,7 @@ org.springframework.cloud.contract.spec.Contract.make { ...@@ -22,6 +22,7 @@ org.springframework.cloud.contract.spec.Contract.make {
body(HystrixContractUtils.simpleBody()) body(HystrixContractUtils.simpleBody())
testMatchers { testMatchers {
jsonPath('$.origin', byCommand('assertOrigin($it)')) jsonPath('$.origin', byCommand('assertOrigin($it)'))
jsonPath('$.event', byCommand('assertEvent($it)'))
jsonPath('$.data', byCommand('assertData($it)')) jsonPath('$.data', byCommand('assertData($it)'))
} }
} }
......
...@@ -50,6 +50,7 @@ import rx.subjects.PublishSubject; ...@@ -50,6 +50,7 @@ import rx.subjects.PublishSubject;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Daniel Lavoie
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(TurbineStreamProperties.class) @EnableConfigurationProperties(TurbineStreamProperties.class)
...@@ -103,6 +104,9 @@ public class TurbineStreamConfiguration implements SmartLifecycle { ...@@ -103,6 +104,9 @@ public class TurbineStreamConfiguration implements SmartLifecycle {
return output.doOnUnsubscribe( return output.doOnUnsubscribe(
() -> log.info("Unsubscribing RxNetty server connection")) () -> log.info("Unsubscribing RxNetty server connection"))
.flatMap(data -> response.writeAndFlush(new ServerSentEvent( .flatMap(data -> response.writeAndFlush(new ServerSentEvent(
null,
Unpooled.copiedBuffer("message",
StandardCharsets.UTF_8),
Unpooled.copiedBuffer(JsonUtility.mapToJson(data), Unpooled.copiedBuffer(JsonUtility.mapToJson(data),
StandardCharsets.UTF_8)))); StandardCharsets.UTF_8))));
}, serveSseConfigurator()); }, serveSseConfigurator());
......
...@@ -16,16 +16,15 @@ ...@@ -16,16 +16,15 @@
package org.springframework.cloud.netflix.turbine.stream; package org.springframework.cloud.netflix.turbine.stream;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -49,13 +48,13 @@ import org.springframework.http.client.ClientHttpResponse; ...@@ -49,13 +48,13 @@ import org.springframework.http.client.ClientHttpResponse;
import org.springframework.integration.support.management.MessageChannelMetrics; import org.springframework.integration.support.management.MessageChannelMetrics;
import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.SubscribableChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Daniel Lavoie
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TurbineStreamTests.Application.class, webEnvironment = WebEnvironment.NONE, value = { @SpringBootTest(classes = TurbineStreamTests.Application.class, webEnvironment = WebEnvironment.NONE, value = {
...@@ -68,9 +67,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -68,9 +67,6 @@ import static org.assertj.core.api.Assertions.assertThat;
"stubrunner.ids=org.springframework.cloud:spring-cloud-netflix-hystrix-stream:${projectVersion:2.0.0.BUILD-SNAPSHOT}:stubs" }) "stubrunner.ids=org.springframework.cloud:spring-cloud-netflix-hystrix-stream:${projectVersion:2.0.0.BUILD-SNAPSHOT}:stubs" })
@AutoConfigureStubRunner @AutoConfigureStubRunner
public class TurbineStreamTests { public class TurbineStreamTests {
private static Log log = LogFactory.getLog(TurbineStreamTests.class);
@Autowired @Autowired
StubTrigger stubTrigger; StubTrigger stubTrigger;
...@@ -86,8 +82,6 @@ public class TurbineStreamTests { ...@@ -86,8 +82,6 @@ public class TurbineStreamTests {
@Autowired @Autowired
TurbineStreamConfiguration turbine; TurbineStreamConfiguration turbine;
private CountDownLatch latch = new CountDownLatch(1);
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableTurbineStream @EnableTurbineStream
public static class Application { public static class Application {
...@@ -109,15 +103,15 @@ public class TurbineStreamTests { ...@@ -109,15 +103,15 @@ public class TurbineStreamTests {
assertThat(((MessageChannelMetrics) input).getSendCount()).isEqualTo(count + 1); assertThat(((MessageChannelMetrics) input).getSendCount()).isEqualTo(count + 1);
} }
private boolean containsMetrics(String line) {
return line.startsWith("data:") && !line.contains("Ping");
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, Object> extractMetrics(String body) throws Exception { private Map<String, Object> extractMetrics(String body) throws Exception {
String[] split = body.split("data:"); for (String value : body.split("\n")) {
for (String value : split) { if (containsMetrics(value)) {
if (value.contains("Ping") || value.length() == 0) { return mapper.readValue(value.split("data:")[1], Map.class);
continue;
}
else {
return mapper.readValue(value, Map.class);
} }
} }
return null; return null;
...@@ -128,21 +122,23 @@ public class TurbineStreamTests { ...@@ -128,21 +122,23 @@ public class TurbineStreamTests {
// The message has to be sent after the endpoint is activated, so this is a // The message has to be sent after the endpoint is activated, so this is a
// convenient place to put it // convenient place to put it
stubTrigger.trigger("metrics"); stubTrigger.trigger("metrics");
byte[] bytes = new byte[1024];
StringBuilder builder = new StringBuilder(); String responseBody = "";
int read = 0; boolean metricFound = false;
while (read >= 0 try (BufferedReader buffer = new BufferedReader(
&& StringUtils.countOccurrencesOf(builder.toString(), "\n") < 2) { new InputStreamReader(response.getBody()))) {
read = response.getBody().read(bytes, 0, bytes.length); do {
if (read > 0) { String line = buffer.readLine();
latch.countDown(); responseBody += line + "\n";
builder.append(new String(bytes, 0, read)); if (containsMetrics(line)) {
} metricFound = true;
log.debug("Building: " + builder); }
} }
log.debug("Done: " + builder); while (!metricFound);
}
return ResponseEntity.status(response.getStatusCode()) return ResponseEntity.status(response.getStatusCode())
.headers(response.getHeaders()).body(builder.toString()); .headers(response.getHeaders()).body(responseBody);
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment