你的位置:首页 > 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.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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 @SpringBootApplication14 @EnableSwagger215 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 @Component10 @Getter @Setter11 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   @Bean12   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   @Override18   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 @Setter11 @AllArgsConstructor12 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、拉取可用服务列表(服务发现)serverList17  * 2、缓存到本地guava cache中去,以后每隔10min从consulServer拉取一次(这里这样做的原因,是因为consul没有做这样的事)18  * 3、使用配置好的路由算法选出其中1台,执行逻辑19 */20 @Component21 public class MyLoadBalancer {22   23   @Autowired24   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 client33     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 @Component13 public class RestAdapterConfig {14 15   @Autowired16   private MyLoadBalancer myLoadBalancer;17 18   /**19    * 负载均衡并且创建传入的API接口实例20   */21   public <T> T create(Class<T> tclass, String serviceName) {22     String commandGroupKey = tclass.getSimpleName();// 获得简单类名作为groupKey23 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   @Override26   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   @Override35   public Response run() throws Exception {36     com.squareup.okhttp.Request okReq = retroReq2okReq(request, server);// 将retrofit类型的request转化为okhttp类型的request37     com.squareup.okhttp.Response okRes = new OkHttpClient().newCall(okReq).execute();38     return okResToRetroRes(okRes);// 将okhttp的response转化为retrofit的response39   }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   @Override79   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的错误处理器)

View Code

说明:当发生了retrofit.error时(不只是上边的getFallback()返回的Response),我们可以在该ErrorHandler的handleError方法来进行相应Response的处理。这里我们指定当404时返回一个自定义异常。

1.5.5、HystrixRuntimeException(自定义异常)

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,并设置mime13 */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/json24   */25   @Override26   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   @Override35   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   @Override19   public String mimeType() {20     return wrapped.contentType().type();21   }22 23   /** Length in bytes. Returns {@code -1} if length is unknown. */24   @Override25   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   @Override38   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.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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=myserviceA2 service.port=80803 service.tag=dev4 health.url=http://localhost:8080/health5 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.faster 4  5 import lombok.AllArgsConstructor; 6 import lombok.Getter; 7 import lombok.NoArgsConstructor; 8 import lombok.Setter; 9 10 /**11  * 省12 */13 @Getter14 @Setter15 @NoArgsConstructor16 @AllArgsConstructor17 @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 @RestController22 @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.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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.1、Province(构造返回的模型类)

View Code

2.2.2、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.3、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 @Component11 public class MyserviceAClient {12 13   @Autowired14   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.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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=myserviceB2 service.port=80813 service.tag=dev4 health.url=http://localhost:8080/health5 health.interval=106 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   @Bean12   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 @RestController18 @RequestMapping("/myserviceB")19 public class MyserviceBController {20 21   @Autowired22   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测试就好了!!!