Zuul 主要功能就是转发,在转发过程中我们无法保证被转发的服务是可用的,这个时候就需要容错机制及回退机制。
容错,简单来说就是当某个服务不可用时,能够切换到其他可用的服务上去,也就是需要有重试机制。在 Zuul 中开启重试机制需要依赖 spring-retry。
首先在 pom.xml 中添加 spring-retry 的依赖,代码如下所示。
在属性文件中开启重试机制以及配置重试次数:
其中:
可以启动两个 hystrix-feign-demo 服务,默认 Ribbon 的转发规则是轮询,然后我们停掉一个 hystrix-feign-demo 服务。没加重试机制之前,当你请求接口的时候肯定有一次是会被转发到停掉的服务上去的,返回的是异常信息。
当我们加入了重试机制后,你可以循环请求接口,这个时候不会返回异常信息,因为 Ribbon 会根据重试配置进行重试,当请求失败后会将请求重新转发到可用的服务上去。
在 Spring Cloud 中,Zuul 默认整合了 Hystrix,当后端服务异常时可以为 Zuul 添加回退功能,返回默认的数据给客户端。
实现回退机制需要实现 ZuulFallbackProvider 接口,代码如下所示。
@Component
public class ServiceConsumerFallbackProvider implements ZuulFallbackProvider {
private Logger log = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
if (cause != null) {
log.error("", cause.getCause());
}
RequestContext ctx = RequestContext.getCurrentContext();
ResponseData data = ResponseData.fail("服务器内部错误 ", ResponseCode.SERVER_ERROR_CODE.getCode());
return new ByteArrayInputStream(JsonUtils.toJson(data).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}
getRoute 方法中返回*表示对所有服务进行回退操作,如果只想对某个服务进行回退,那么就返回需要回退的服务名称,这个名称一定要是注册到 Eureka 中的名称。
通过 ClientHttpResponse 构造回退的内容。通过 getStatusCode 返回响应的状态码。通过 getStatusText 返回响应状态码对应的文本。通过 getBody 返回回退的内容。通过 getHeaders 返回响应的请求头信息。
通过 API 网关来访问 hystrix-feign-demo 服务,将 hystrix-feign-demo 服务停掉,然后再次访问,就可以看到回退的内容了,如图 1 所示。
跟业务相关的服务我们都是注册到 Eureka 中,通过 Ribbon 来进行负载均衡,服务可以通过水平扩展来实现高可用。
现实使用中,API 网关这层往往是给 APP、Webapp、客户来调用接口的,如果我们将 Zuul 也注册到 Eureka 中是达不到高可用的,因为你不可能让你的客户也去操作你的注册中心。
这时最好的办法就是用额外的负载均衡器来实现 Zuul 的高可用,比如我们最常用的 Nginx,或者 HAProxy、F5 等。
这种方式也是单体项目最常用的负载方式,当用户请求一个地址的时候,通过 Nginx 去做转发,当一个服务挂掉的时候,Nginx 会把它排除掉。
如果想要 API 网关也能随时水平扩展,那么我们可以用脚本来动态修改 Nginx 的配置,通过脚本操作 Eureka,发现有新加入的网关服务或者下线的网关服务,直接修改 Nginx 的 upstream,然后通过重载(reload)配置来达到网关的动态扩容。
如果不用脚本结合注册中心去做的话,就只能提前规划好 N 个节点,然后手动配置上去。