第四章 服務容錯,第四章容錯
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151602.png)
上一節,描述了服務發現、負載均衡以及服務之間的調用。到這裡,加上第二節的服務注冊,整個微服務的架構就已經搭建出來了,即功能性需求就完成了。從本節開始的記錄其實全部都是非功能性需求。
一、集群容錯
技術選型:hystrix。(就是上圖中熔斷器)
熔斷的作用:
第一個作用:
假設有兩台服務器server1(假設可以處理的請求阈值是1W請求)和server2,在server1上注冊了三個服務service1、service2、service3,在server2上注冊了一個服務service4,假設service4服務響應緩慢,service1調用service4時,一直在等待響應,那麼在高並發下,很快的server1處很快就會達到請求阈值(server1很快就會耗盡處理線程)之後可能宕機,這時候,不只是service1不再可用,server1上的service2和service3也不可用了。
如果我們引入了hystrix,那麼service1調用service4的時候,當發現service4超時,立即斷掉不再執行,執行getFallback邏輯。這樣的話,server1就不會耗盡處理線程,server1上的其他服務也是可用的。當然,這是在合理的配置了超時時間的情況下,如果超時時間設置的太長的話,還是會出現未引入hystrix之前的情況。
第二個作用:
當被調服務經常失敗,比如說在10min(可配)中之內調用了20次,失敗了15次(可配),那麼我們認為這個服務是失敗的,先關閉該服務,等一會兒後再自動重新啟動該服務!(這是真正的熔斷!)
二、實現
由於代碼變動比較大,我會列出全部關鍵代碼。
1、framework
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151643.png)
1.1、pom.xml
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4
5 <modelVersion>4.0.0</modelVersion>
6
7 <parent>
8 <groupId>org.springframework.boot</groupId>
9 <artifactId>spring-boot-starter-parent</artifactId>
10 <version>1.3.0.RELEASE</version>
11 </parent>
12
13 <groupId>com.microservice</groupId>
14 <artifactId>framework</artifactId>
15 <version>1.0-SNAPSHOT</version>
16
17 <properties>
18 <java.version>1.8</java.version><!-- 官方推薦 -->
19 </properties>
20
21 <!-- 引入實際依賴 -->
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-web</artifactId>
26 </dependency>
27 <!-- consul-client -->
28 <dependency>
29 <groupId>com.orbitz.consul</groupId>
30 <artifactId>consul-client</artifactId>
31 <version>0.10.0</version>
32 </dependency>
33 <!-- consul需要的包 -->
34 <dependency>
35 <groupId>org.glassfish.jersey.core</groupId>
36 <artifactId>jersey-client</artifactId>
37 <version>2.22.2</version>
38 </dependency>
39 <dependency>
40 <groupId>com.alibaba</groupId>
41 <artifactId>fastjson</artifactId>
42 <version>1.1.15</version>
43 </dependency>
44 <!-- 引入監控工具,包含health檢查(用於consul注冊) -->
45 <dependency>
46 <groupId>org.springframework.boot</groupId>
47 <artifactId>spring-boot-starter-actuator</artifactId>
48 </dependency>
49 <!-- 引入lombok,簡化pojo -->
50 <dependency>
51 <groupId>org.projectlombok</groupId>
52 <artifactId>lombok</artifactId>
53 <version>1.16.8</version>
54 </dependency>
55 <!-- 引入swagger2 -->
56 <dependency>
57 <groupId>io.springfox</groupId>
58 <artifactId>springfox-swagger2</artifactId>
59 <version>2.2.2</version>
60 </dependency>
61 <dependency>
62 <groupId>io.springfox</groupId>
63 <artifactId>springfox-swagger-ui</artifactId>
64 <version>2.2.2</version>
65 </dependency>
66 <!-- retrofit -->
67 <dependency>
68 <groupId>com.squareup.retrofit</groupId>
69 <artifactId>retrofit</artifactId>
70 <version>1.9.0</version>
71 </dependency>
72 <!-- converter-jackson -->
73 <dependency>
74 <groupId>com.squareup.retrofit</groupId>
75 <artifactId>converter-jackson</artifactId>
76 <version>1.9.0</version>
77 </dependency>
78 <!-- okhttp -->
79 <dependency>
80 <groupId>com.squareup.okhttp</groupId>
81 <artifactId>okhttp</artifactId>
82 <version>2.4.0</version>
83 </dependency>
84 <!-- hystrix -->
85 <dependency>
86 <groupId>com.netflix.hystrix</groupId>
87 <artifactId>hystrix-core</artifactId>
88 <version>1.5.3</version>
89 </dependency>
90 <dependency>
91 <groupId>com.netflix.hystrix</groupId>
92 <artifactId>hystrix-metrics-event-stream</artifactId>
93 <version>1.5.3</version>
94 </dependency>
95 </dependencies>
96
97 <build>
98 <plugins>
99 <plugin>
100 <groupId>org.springframework.boot</groupId>
101 <artifactId>spring-boot-maven-plugin</artifactId>
102 </plugin>
103 </plugins>
104 </build>
105 </project>
View Code
1.2、啟動類
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice;
2
3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6 import com.microservice.consul.ConsulRegisterListener;
7
8 import springfox.documentation.swagger2.annotations.EnableSwagger2;
9
10 /**
11 * 注意:@SpringBootApplication該注解必須在SpringApplication.run()所在的類上
12 */
13 @SpringBootApplication
14 @EnableSwagger2
15 public class MySpringAplication {
16
17 public void run(String[] args) {
18 SpringApplication sa = new SpringApplication(MySpringAplication.class);
19 sa.addListeners(new ConsulRegisterListener());
20 sa.run(args);
21 }
22
23 public static void main(String[] args) {
24 }
25 }
View Code
1.3、服務注冊(consul包)
1.3.1、ConsulProperties
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.consul;
2
3 import org.springframework.beans.factory.annotation.Value;
4 import org.springframework.stereotype.Component;
5
6 import lombok.Getter;
7 import lombok.Setter;
8
9 @Component
10 @Getter @Setter
11 public class ConsulProperties {
12
13 @Value("${service.name}")
14 private String servicename;
15 @Value("${service.port:8080}")
16 private int servicePort;
17 @Value("${service.tag:dev}")
18 private String serviceTag;
19 @Value("${health.url}")
20 private String healthUrl;
21 @Value("${health.interval:10}")
22 private int healthInterval;
23
24 }
View Code
1.3.2、ConsulConfig
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.consul;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 import com.orbitz.consul.Consul;
7
8 @Configuration
9 public class ConsulConfig {
10
11 @Bean
12 public Consul consul(){
13 return Consul.builder().build();
14 }
15 }
View Code
1.3.3、ConsulRegisterListener
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.consul;
2
3 import java.net.MalformedURLException;
4 import java.net.URI;
5
6 import org.springframework.context.ApplicationListener;
7 import org.springframework.context.event.ContextRefreshedEvent;
8
9 import com.orbitz.consul.AgentClient;
10 import com.orbitz.consul.Consul;
11
12 /**
13 * 監聽contextrefresh事件
14 */
15 public class ConsulRegisterListener implements ApplicationListener<ContextRefreshedEvent> {
16
17 @Override
18 public void onApplicationEvent(ContextRefreshedEvent event) {
19 Consul consul = event.getApplicationContext().getBean(Consul.class);
20 ConsulProperties prop = event.getApplicationContext().getBean(ConsulProperties.class);
21
22 AgentClient agentClient = consul.agentClient();
23 try {
24 agentClient.register(prop.getServicePort(),
25 URI.create(prop.getHealthUrl()).toURL(),
26 prop.getHealthInterval(),
27 prop.getServicename(),
28 prop.getServicename(), // serviceId:
29 prop.getServiceTag());
30 } catch (MalformedURLException e) {
31 e.printStackTrace();
32 }
33 }
34
35 }
View Code
1.4、服務發現+負載均衡(loadBalance包)
1.4.1、ServerAddress
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.loadBalancer;
2
3 import lombok.AllArgsConstructor;
4 import lombok.Getter;
5 import lombok.Setter;
6
7 /**
8 * 這裡只做簡單的封裝,如果需要復雜的,可以使用java.net.InetAddress類
9 */
10 @Getter @Setter
11 @AllArgsConstructor
12 public class ServerAddress {
13 private String ip;
14 private int port;
15 }
View Code
1.4.2、MyLoadBalancer
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.loadBalancer;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Random;
6
7 import org.springframework.beans.factory.annotation.Autowired;
8 import org.springframework.stereotype.Component;
9
10 import com.orbitz.consul.Consul;
11 import com.orbitz.consul.HealthClient;
12 import com.orbitz.consul.model.health.ServiceHealth;
13
14 /**
15 * 實現思路:
16 * 1、拉取可用服務列表(服務發現)serverList
17 * 2、緩存到本地guava cache中去,以後每隔10min從consulServer拉取一次(這裡這樣做的原因,是因為consul沒有做這樣的事)
18 * 3、使用配置好的路由算法選出其中1台,執行邏輯
19 */
20 @Component
21 public class MyLoadBalancer {
22
23 @Autowired
24 private Consul consul;
25
26 /**
27 * 獲取被調服務的服務列表
28 * @param serviceName 被調服務
29 */
30 public List<ServerAddress> getAvailableServerList(String serviceName){
31 List<ServerAddress> availableServerList = new ArrayList<>();
32 HealthClient healthClient = consul.healthClient();//獲取Health http client
33 List<ServiceHealth> availableServers = healthClient.getHealthyServiceInstances(serviceName).getResponse();//從本地agent查找所有可用節點
34 availableServers.forEach(x->availableServerList.add(new ServerAddress(x.getNode().getAddress(), x.getService().getPort())));
35 return availableServerList;
36 }
37
38 /**
39 * 選擇一台服務器
40 * 這裡使用隨機算法,如果需要換算法,我們可以抽取接口進行編寫
41 */
42 public ServerAddress chooseServer(String serviceName){
43 List<ServerAddress> servers = getAvailableServerList(serviceName);
44 Random random = new Random();
45 int index = random.nextInt(servers.size());
46 return servers.get(index);
47 }
48
49 }
View Code
以上代碼均與第三節一樣。這的負載均衡之後會用ribbon來做。
1.5、服務通信(retrofit)+集群容錯(hystrix)
注意:這裡我先給出代碼,最後我會好好的說一下調用流程。
1.5.1、RestAdapterConfig
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.retrofit;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.stereotype.Component;
5
6 import com.microservice.loadBalancer.MyLoadBalancer;
7 import com.microservice.loadBalancer.ServerAddress;
8
9 import retrofit.RestAdapter;
10 import retrofit.converter.JacksonConverter;
11
12 @Component
13 public class RestAdapterConfig {
14
15 @Autowired
16 private MyLoadBalancer myLoadBalancer;
17
18 /**
19 * 負載均衡並且創建傳入的API接口實例
20 */
21 public <T> T create(Class<T> tclass, String serviceName) {
22 String commandGroupKey = tclass.getSimpleName();// 獲得簡單類名作為groupKey
23
24 ServerAddress server = myLoadBalancer.chooseServer(serviceName);// 負載均衡
25 RestAdapter restAdapter = new RestAdapter.Builder()
26 .setConverter(new JacksonConverter())
27 .setErrorHandler(new MyErrorHandler())
28 .setClient(new MyHttpClient(server, commandGroupKey))
29 .setEndpoint("/").build();
30 T tclassInstance = restAdapter.create(tclass);
31 return tclassInstance;
32 }
33 }
View Code
說明:這裡我們定義了自己的retrofit.Client和自己的retrofit.ErrorHandler
1.5.2、MyHttpClient(自定義retrofit的Client)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.retrofit;
2
3 import java.io.IOException;
4
5 import com.microservice.hystrix.HttpHystrixCommand;
6 import com.microservice.loadBalancer.ServerAddress;
7 import com.netflix.hystrix.HystrixCommand.Setter;
8 import com.netflix.hystrix.HystrixCommandGroupKey;
9 import com.netflix.hystrix.HystrixCommandProperties;
10
11 import retrofit.client.Client;
12 import retrofit.client.Request;
13 import retrofit.client.Response;
14
15 public class MyHttpClient implements Client {
16 private ServerAddress server;
17 private String commandGroupKey;
18 private int hystrixTimeoutInMillions = 3000;// 這裡暫且將數據硬編碼在這裡(之後會改造)
19
20 public MyHttpClient(ServerAddress server, String commandGroupKey) {
21 this.server = server;
22 this.commandGroupKey = commandGroupKey;
23 }
24
25 @Override
26 public Response execute(Request request) throws IOException {
27 Setter setter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(commandGroupKey));
28 setter.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(hystrixTimeoutInMillions));
29 return new HttpHystrixCommand(setter, server, request).execute();// 同步執行
30 }
31 }
View Code
說明:在execute()中引入了hystrix
- 定義了hystrix的commandGroupKey是服務名(eg.myserviceA,被調用服務名)
- 沒有定義commandKey(通常commandKey是服務的一個方法名,例如myserviceA的client的getProvinceByCityName),通常該方法名是被調用服務的client中的被調用方法名
- 硬編碼了hystrix的超時時間(這裡的硬編碼會通過之後的配置集中管理來處理)
1.5.3、HttpHystrixCommand(hystrix核心類)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.hystrix;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8
9 import com.microservice.loadBalancer.ServerAddress;
10 import com.microservice.retrofit.RequestBodyWrapper;
11 import com.microservice.retrofit.ResponseBodyWrapper;
12 import com.netflix.hystrix.HystrixCommand;
13 import com.squareup.okhttp.Headers;
14 import com.squareup.okhttp.OkHttpClient;
15 import com.squareup.okhttp.Request.Builder;
16
17 import retrofit.client.Header;
18 import retrofit.client.Request;
19 import retrofit.client.Response;
20
21 public class HttpHystrixCommand extends HystrixCommand<Response> {
22 private static final Logger LOGGER = LoggerFactory.getLogger(HttpHystrixCommand.class);
23
24 private ServerAddress server;
25 private Request request;
26 private String requestUrl;
27
28 public HttpHystrixCommand(Setter setter, ServerAddress server, Request request) {
29 super(setter);
30 this.server = server;
31 this.request = request;
32 }
33
34 @Override
35 public Response run() throws Exception {
36 com.squareup.okhttp.Request okReq = retroReq2okReq(request, server);// 將retrofit類型的request轉化為okhttp類型的request
37 com.squareup.okhttp.Response okRes = new OkHttpClient().newCall(okReq).execute();
38 return okResToRetroRes(okRes);// 將okhttp的response轉化為retrofit的response
39 }
40
41 public com.squareup.okhttp.Request retroReq2okReq(Request request, ServerAddress server) {
42 Builder requestBuilder = new Builder();
43 /***********************1、將retrofit的header轉化為ok的header*************************/
44 List<Header> headers = request.getHeaders();
45 Headers.Builder okHeadersBulder = new Headers.Builder();
46 headers.forEach(x -> okHeadersBulder.add(x.getName(), x.getValue()));
47 requestBuilder.headers(okHeadersBulder.build());
48 /***********************2、根據之前負載均衡策略選出的機器構建訪問URL*************************/
49 String url = new StringBuilder("http://").append(server.getIp()).append(":").append(server.getPort())
50 .append("/").append(request.getUrl()).toString();
51 requestUrl = url;
52 requestBuilder.url(url);
53 /***********************3、構造方法請求類型和請求體(GET是沒有請求體的,這裡就是null)**********/
54 requestBuilder.method(request.getMethod(), new RequestBodyWrapper(request.getBody()));
55 return requestBuilder.build();
56 }
57
58 public Response okResToRetroRes(com.squareup.okhttp.Response okRes) {
59 return new Response(okRes.request().urlString(),
60 okRes.code(),
61 okRes.message(),
62 getHeaders(okRes.headers()),
63 new ResponseBodyWrapper(okRes.body()));
64 }
65
66 private List<Header> getHeaders(Headers okHeaders) {
67 List<Header> retrofitHeaders = new ArrayList<>();
68 int count = okHeaders.size();
69 for (int i = 0; i < count; i++) {
70 retrofitHeaders.add(new Header(okHeaders.name(i), okHeaders.value(i)));
71 }
72 return retrofitHeaders;
73 }
74
75 /**
76 * 超時後的一些操作,或者如果緩存中有信息,可以從緩存中拿一些,具體的要看業務,也可以打一些logger TODO 這裡調用失敗了
77 */
78 @Override
79 public Response getFallback() {
80 LOGGER.error("請求超時了!requestUrl:'{}'", requestUrl);
81 /**
82 * 想要讓自定義的ErrorHandler起作用以及下邊的404和reason有意義,就一定要配置requestUrl和List<header>
83 * 其實這裡可以看做是定義自定義異常的狀態碼和狀態描述
84 * 其中狀態碼用於自定義異常中的判斷(見HystrixRuntimeException)
85 */
86 return new Response(requestUrl,
87 404, //定義狀態碼
88 "execute getFallback because execution timeout",//定義消息
89 new ArrayList<Header>(),null);
90 }
91 }
View Code
說明:首先調用run(),run()失敗或超時候調用getFallback()
- run()--這裡是一個定制口,我使用了okhttp,還可以使用其他的網絡調用工具
- 首先將Retrofit的請求信息Request轉化為Okhttp的Request(在這裡調用了負載均衡,將請求負載到選出的一台機器)
- 之後調用Okhttp來進行真正的http調用,並返回okhttp型的相應Response
- 最後將okhttp型的響應Response轉換為Retrofit型的Response
- getFallback()
- 直接拋異常是不行的(該接口不讓),只能采取以下的方式
- 返回一個Response對象,該對象封裝了status是404+錯誤的原因reason+請求的url+相應的Header列表+響應體(這裡的status和reason會被用在ErrorHandler中去用於指定執行不同的邏輯,具體看下邊的MyErrorHandler)
- 如果想讓MyErrorHandler起作用,Response對象必須有"請求的url+相應的Header列表",其中Header列表可以使一個空List實現類,但是不可為null
1.5.4、MyErrorHandler(自定義retrofit的錯誤處理器)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.retrofit;
2
3 import com.microservice.exception.HystrixRuntimeException;
4
5 import retrofit.ErrorHandler;
6 import retrofit.RetrofitError;
7 import retrofit.client.Response;
8
9 public class MyErrorHandler implements ErrorHandler{
10 @Override
11 public Throwable handleError(RetrofitError cause) {
12 Response response = cause.getResponse();
13 /**
14 * 這裡是一個可以定制的地方,自己可以定義所有想要捕獲的異常
15 */
16 if(response!=null && response.getStatus()==404){
17 return new HystrixRuntimeException(cause);
18 }
19 return cause;
20 }
21 }
View Code
說明:當發生了retrofit.error時(不只是上邊的getFallback()返回的Response),我們可以在該ErrorHandler的handleError方法來進行相應Response的處理。這裡我們指定當404時返回一個自定義異常。
1.5.5、HystrixRuntimeException(自定義異常)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.exception;
2
3 /**
4 * 自定義異常
5 */
6 public class HystrixRuntimeException extends RuntimeException {
7 private static final long serialVersionUID = 8252124808929848902L;
8
9 public HystrixRuntimeException(Throwable cause) {
10 super(cause);//只有這樣,才能將異常信息拋給客戶端
11 }
12 }
View Code
說明:自定義異常只能通過super()來向客戶端拋出自己指定的異常信息(上邊的Response的reason,但是拋到客戶端時還是一個500錯誤,因為run()錯誤或超時就是一個服務端錯誤)。
1.5.6、RequestBodyWrapper(將TypedOutput轉化成RequestBody的工具類)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.retrofit;
2
3 import java.io.IOException;
4
5 import com.squareup.okhttp.MediaType;
6 import com.squareup.okhttp.RequestBody;
7
8 import okio.BufferedSink;
9 import retrofit.mime.TypedOutput;
10
11 /**
12 * 將TypedOutput轉為RequestBody,並設置mime
13 */
14 public class RequestBodyWrapper extends RequestBody{
15 private final TypedOutput wrapped;
16
17 public RequestBodyWrapper(TypedOutput body) {
18 this.wrapped = body;
19 }
20
21 /**
22 * 首先獲取retrofit中的request請求的mime類型,即Content-Type,
23 * 如果為null,就返回application/json
24 */
25 @Override
26 public MediaType contentType() {
27 if (wrapped != null && wrapped.mimeType() != null) {
28 return MediaType.parse(wrapped.mimeType());
29 }
30 return MediaType.parse("application/json; charset=UTF-8");
31 }
32
33 /** Writes the content of this request to {@code out}. */
34 @Override
35 public void writeTo(BufferedSink sink) throws IOException {
36 if (wrapped != null) {
37 wrapped.writeTo(sink.outputStream());
38 }
39 }
40 }
View Code
說明:該方法是將TypedOutput轉化成RequestBody的工具類(用於在將retrofit.Request轉化為okhttp.Request的時候的請求方法體的封裝)
1.5.7、ResponseBodyWrapper(將ResponseBody轉化為TypedInput的工具類)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.retrofit;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5
6 import com.squareup.okhttp.ResponseBody;
7
8 import retrofit.mime.TypedInput;
9
10 public class ResponseBodyWrapper implements TypedInput {
11 private final ResponseBody wrapped;
12
13 public ResponseBodyWrapper(ResponseBody body) {
14 this.wrapped = body;
15 }
16
17 /** Returns the mime type. */
18 @Override
19 public String mimeType() {
20 return wrapped.contentType().type();
21 }
22
23 /** Length in bytes. Returns {@code -1} if length is unknown. */
24 @Override
25 public long length() {
26 try {
27 return wrapped.contentLength();
28 } catch (IOException e) {
29 e.printStackTrace();
30 }
31 return 0;
32 }
33
34 /**
35 * Read bytes as stream.
36 */
37 @Override
38 public InputStream in() throws IOException {
39 return wrapped.byteStream();
40 }
41 }
View Code
說明:該方法是將ResponseBody轉化為TypedInput的工具類(用於在講okhttp.Response轉化為retrofit.Response的時候響應體的封裝)
小結:
- retrofit:請求方法體TypedOutput,響應體TypedInput
- okhttp:請求方法體RequestBody,響應體ResponseBody
整個流程:
當myserviceB調用myserviceA的一個方法時,首先會執行自定義的MyHttpClient的execute()方法,在該execute()方法中我們執行了自定義的HttpHystrixCommand的execute()方法,此時就會執行執行HttpHystrixCommand的run()方法,如果該方法運行正常並在超時時間內返回數據,則調用結束。
如果run()方法調用失敗或該方法超時,就會直接運行HttpHystrixCommand的getFallback()方法。該方法返回一個retrofit.Response對象,該對象的status是404,錯誤信息也是自定義的。之後該對象會被包裝到RetrofitError對象中,之後RetrofitError對象會由MyErrorHandler的handleError()進行處理:從RetrofitError對象中先取出Response,之後根據該Response的status執行相應的操作,我們這裡對404的情況定義了一個自定義異常HystrixRuntimeException。
注意點:
- retrofit的Response最好不要是null
- retrofit的Jackson轉換器無法轉化單純的String(因為Jackson轉換器會將一個json串轉化為json對象),這一點缺點可以看做沒有,因為我們的接口都是restful的,那麼我們都是使用json格式來通信的。
2、myserviceA
2.1、myserviceA-server
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151605.png)
2.1.1、pom.xml
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4
5 <modelVersion>4.0.0</modelVersion>
6
7 <parent>
8 <groupId>com.microservice</groupId>
9 <artifactId>myserviceA</artifactId>
10 <version>1.0-SNAPSHOT</version>
11 </parent>
12
13 <artifactId>myserviceA-server</artifactId>
14
15 <!-- 引入實際依賴 -->
16 <dependencies>
17 <dependency>
18 <groupId>com.alibaba</groupId>
19 <artifactId>fastjson</artifactId>
20 <version>1.1.15</version>
21 </dependency>
22 </dependencies>
23
24 <build>
25 <plugins>
26 <plugin>
27 <groupId>org.springframework.boot</groupId>
28 <artifactId>spring-boot-maven-plugin</artifactId>
29 </plugin>
30 </plugins>
31 </build>
32 </project>
View Code
2.1.2、application.properties
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 service.name=myserviceA
2 service.port=8080
3 service.tag=dev
4 health.url=http://localhost:8080/health
5 health.interval=10
View Code
2.1.3、啟動類
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA;
2
3 import org.springframework.boot.autoconfigure.SpringBootApplication;
4
5 import com.microservice.MySpringAplication;
6
7 @SpringBootApplication
8 public class MyServiceAApplication {
9
10 public static void main(String[] args) {
11 MySpringAplication mySpringAplication = new MySpringAplication();
12 mySpringAplication.run(args);
13 }
14 }
View Code
2.1.4、Province(構造返回的模型類)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA.model;
2
3 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4
5 import lombok.AllArgsConstructor;
6 import lombok.Getter;
7 import lombok.NoArgsConstructor;
8 import lombok.Setter;
9
10 /**
11 * 省
12 */
13 @Getter
14 @Setter
15 @NoArgsConstructor
16 @AllArgsConstructor
17 @JsonIgnoreProperties(ignoreUnknown = true)
18 public class Province {
19 private int id;
20 private String provinceName;// 省份名稱
21 private long personNum; // 人口數量
22 }
View Code
說明:實際上一些返回值的模型類一般不僅會在server中用到,也會在client中用到。所以一般我們還會建立一個myserviceA-common模塊,該模塊專門用於存放server和client公共用到的一些東西。
2.1.5、MyserviceAController
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA.controller;
2
3 import org.apache.commons.lang3.builder.ToStringBuilder;
4 import org.apache.commons.lang3.exception.ExceptionUtils;
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7 import org.springframework.web.bind.annotation.PathVariable;
8 import org.springframework.web.bind.annotation.RequestMapping;
9 import org.springframework.web.bind.annotation.RequestMethod;
10 import org.springframework.web.bind.annotation.RequestParam;
11 import org.springframework.web.bind.annotation.RestController;
12
13 import com.microservice.myserviceA.model.Province;
14
15 import io.swagger.annotations.Api;
16 import io.swagger.annotations.ApiImplicitParam;
17 import io.swagger.annotations.ApiImplicitParams;
18 import io.swagger.annotations.ApiOperation;
19
20 @Api("Myservice API")
21 @RestController
22 @RequestMapping("/myserviceA")
23 public class MyserviceAController {
24 private static final Logger LOGGER = LoggerFactory.getLogger(MyserviceAController.class);
25
26 @ApiOperation("創建省份信息並返回")
27 @ApiImplicitParams({ @ApiImplicitParam(name = "provincename", paramType = "path", dataType = "String") })
28 @RequestMapping(value = "/provinces/{provincename}", method = RequestMethod.POST)
29 public Province getProvinceByCityName(@PathVariable("provincename") String provincename,
30 @RequestParam("personNum") long personNum) {
31 long startTime = System.currentTimeMillis();
32 LOGGER.info("start - MyserviceAController:getProvinceByCityName,provincename:'{}',personNum:'{}'", provincename, personNum);
33 try {
34 Thread.sleep(5000);
35 Province province = new Province(1, provincename, personNum);
36 LOGGER.info("end - MyserviceAController:getProvinceByCityName,province:'{}',耗時:'{}'", ToStringBuilder.reflectionToString(province),System.currentTimeMillis()-startTime);
37 return province;
38 } catch (Exception e) {
39 LOGGER.error(ExceptionUtils.getStackTrace(e));
40 return new Province();
41 }
42 }
43 }
View Code
說明:注意該controller值目前為止最標准的寫法。
- 打開始日志可結束日志(包括正常結束LOGGER.info和異常結束LOGGER.error)
- 統一try-catch,這樣的話,在service、dao層就不需要再捕獲各種異常了(除非需要)
- 特別推薦:ExceptionUtils.getStackTrace(e),該方法返回值為String,通常只有打出這個stackTrace才有用
2.2、myserviceA-client
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151783.png)
2.2.1、pom.xml
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>com.microservice</groupId>
7 <artifactId>myserviceA</artifactId>
8 <version>1.0-SNAPSHOT</version>
9 </parent>
10
11 <artifactId>myserviceA-client</artifactId>
12 <packaging>jar</packaging>
13 </project>
View Code
2.2.2、Province(構造返回的模型類)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA.model;
2
3 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4
5 import lombok.AllArgsConstructor;
6 import lombok.Getter;
7 import lombok.NoArgsConstructor;
8 import lombok.Setter;
9
10 /**
11 * 省
12 */
13 @Getter
14 @Setter
15 @NoArgsConstructor
16 @AllArgsConstructor
17 @JsonIgnoreProperties(ignoreUnknown = true)
18 public class Province {
19 private int id;
20 private String provinceName;// 省份名稱
21 private long personNum; // 人口數量
22 }
View Code
說明:
- jackson轉換器需要空構造器
- 這種用於json轉換的對象最好加上@JsonIgnoreProperties(ignoreUnknown = true)防止反序的json串的字段多於定義的模型對象的屬性時,拋出反序列化異常,
2.2.3、MyserviceAAPI
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA.api;
2
3 import com.microservice.myserviceA.model.Province;
4
5 import retrofit.http.POST;
6 import retrofit.http.Path;
7 import retrofit.http.Query;
8
9 public interface MyserviceAAPI {
10 @POST("/myserviceA/provinces/{provincename}")
11 public Province getProvinceByCityName(@Path("provincename") String provincename,
12 @Query("personNum") long personNum);
13 }
View Code
2.2.4、MyserviceAClient
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceA.client;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.stereotype.Component;
5
6 import com.microservice.myserviceA.api.MyserviceAAPI;
7 import com.microservice.myserviceA.model.Province;
8 import com.microservice.retrofit.RestAdapterConfig;
9
10 @Component
11 public class MyserviceAClient {
12
13 @Autowired
14 private RestAdapterConfig restAdapterConfig;
15
16 public Province getProvinceByCityName(String provincename, long personNum) {
17 MyserviceAAPI myserviceAAPI = restAdapterConfig.create(MyserviceAAPI.class, "myserviceA");
18 return myserviceAAPI.getProvinceByCityName(provincename, personNum);
19 }
20
21 }
View Code
3、myserviceB
3.1、myserviceB-server
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151732.png)
3.1.1、pom.xml
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4
5 <modelVersion>4.0.0</modelVersion>
6
7 <parent>
8 <groupId>com.microservice</groupId>
9 <artifactId>myserviceB</artifactId>
10 <version>1.0-SNAPSHOT</version>
11 </parent>
12
13 <artifactId>myserviceB-server</artifactId>
14
15 <!-- 引入實際依賴 -->
16 <dependencies>
17 <dependency>
18 <groupId>com.alibaba</groupId>
19 <artifactId>fastjson</artifactId>
20 <version>1.1.15</version>
21 </dependency>
22 <!-- 引入myserviceA-client -->
23 <dependency>
24 <groupId>com.microservice</groupId>
25 <artifactId>myserviceA-client</artifactId>
26 <version>1.0-SNAPSHOT</version>
27 </dependency>
28 </dependencies>
29
30 <build>
31 <plugins>
32 <plugin>
33 <groupId>org.springframework.boot</groupId>
34 <artifactId>spring-boot-maven-plugin</artifactId>
35 </plugin>
36 </plugins>
37 </build>
38 </project>
View Code
3.1.2、application.properties
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 service.name=myserviceB
2 service.port=8081
3 service.tag=dev
4 health.url=http://localhost:8080/health
5 health.interval=10
6
7 server.port=8081
View Code
3.1.3、啟動類
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceB;
2
3 import org.springframework.boot.autoconfigure.SpringBootApplication;
4
5 import com.microservice.MySpringAplication;
6
7 @SpringBootApplication
8 public class MyServiceBApplication {
9
10 public static void main(String[] args) {
11 MySpringAplication mySpringAplication = new MySpringAplication();
12 mySpringAplication.run(args);
13 }
14 }
View Code
3.1.4、MyServiceBConfig
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceB.config;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 import com.microservice.myserviceA.client.MyserviceAClient;
7
8 @Configuration
9 public class MyServiceBConfig {
10
11 @Bean
12 public MyserviceAClient myserviceAClient(){
13 return new MyserviceAClient();
14 }
15 }
View Code
3.1.5、MyserviceBController
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011814151616.gif)
![]()
1 package com.microservice.myserviceB.controller;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.web.bind.annotation.PathVariable;
5 import org.springframework.web.bind.annotation.RequestMapping;
6 import org.springframework.web.bind.annotation.RequestMethod;
7 import org.springframework.web.bind.annotation.RequestParam;
8 import org.springframework.web.bind.annotation.RestController;
9
10 import com.microservice.myserviceA.client.MyserviceAClient;
11 import com.microservice.myserviceA.model.Province;
12
13 import io.swagger.annotations.Api;
14 import io.swagger.annotations.ApiOperation;
15
16 @Api("MyserviceB API")
17 @RestController
18 @RequestMapping("/myserviceB")
19 public class MyserviceBController {
20
21 @Autowired
22 private MyserviceAClient myserviceAClient;
23
24 @ApiOperation("調用myServiceA的client(實現微服務之間的調用)")
25 @RequestMapping(value = "/provinces/{provincename}", method = RequestMethod.POST)
26 public Province getProvinceByCityName(@PathVariable("provincename") String provincename,
27 @RequestParam("personNum") long personNum) {
28 Province provinceInfo = myserviceAClient.getProvinceByCityName(provincename, personNum);
29 return provinceInfo;
30 }
31 }
View Code
最後,啟動consul,啟動服務,swagger測試就好了!!!