码迷,mamicode.com
首页 > 其他好文 > 详细

第四章 服务容错 - 引入hystrix

时间:2016-06-17 22:31:21      阅读:2037      评论:0      收藏:0      [点我收藏+]

标签:

技术分享

上一节,描述了服务发现、负载均衡以及服务之间的调用。到这里,加上第二节的服务注册,整个微服务的架构就已经搭建出来了,即功能性需求就完成了。从本节开始的记录其实全部都是非功能性需求。

一、集群容错

技术选型: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测试就好了!!!

第四章 服务容错 - 引入hystrix

标签:

原文地址:http://www.cnblogs.com/java-zhao/p/5595157.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!