程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 第四章 服務容錯,第四章容錯

第四章 服務容錯,第四章容錯

編輯:JAVA綜合教程

第四章 服務容錯,第四章容錯


上一節,描述了服務發現、負載均衡以及服務之間的調用。到這裡,加上第二節的服務注冊,整個微服務的架構就已經搭建出來了,即功能性需求就完成了。從本節開始的記錄其實全部都是非功能性需求。

一、集群容錯

技術選型: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

1.1、pom.xml

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、啟動類

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

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

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

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

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

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

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)

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核心類)

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的錯誤處理器)

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(自定義異常)

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的工具類)

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的工具類)

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

2.1.1、pom.xml

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

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、啟動類

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(構造返回的模型類)

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

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

2.2.1、pom.xml

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(構造返回的模型類)

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

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

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

3.1.1、pom.xml

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

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、啟動類

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

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

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測試就好了!!!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved