Spring Cloud使用Zuul来作为路由网关,网关的功能对于分布式网站十分重要。Zuul主要有三个功能:
server:
#服务端口
port: 9000
spring:
application:
#服务名称
name: server
eureka:
#配置环境
environment: dev
#配置数据中心
datacenter: nanjing
instance:
#注册服务器名称
hostname: localhost
client:
#是否注册到服务中心
register-with-eureka: false
#是否检索服务
fetch-registry: false
service-url:
#客户端服务域
defaultZone: http://localhost:9000/eureka/
package com.ming.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //开启Eureka服务端
public class ServerEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ServerEurekaApplication.class, args);
}
}
#配置第一个用户微服务
server:
#服务端口
port: 7001
spring:
#区分不同环境下的配置文件(启动工程时使用)
profiles: user1
#服务名称
application:
name: user
eureka:
client:
service-url:
#服务注册地址
defaultZone: http://localhost:9000/eureka/
#隔离线
---
#配置第二个用户微服务
server:
#服务端口
port: 7002
spring:
#区分不同环境下的配置文件(启动工程时使用)
profiles: user2
#服务名称
application:
name: user
eureka:
client:
service-url:
#服务注册地址
defaultZone: http://localhost:9000/eureka/
package com.ming.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端
public class ClientUserApplication {
public static void main(String[] args) {
SpringApplication.run(ClientUserApplication.class, args);
}
}
package com.ming.user.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/timeout")
public String timeout() {
//调用服务时间
Timestamp time = new Timestamp(System.currentTimeMillis());
//生成一个2000之内的随机数
long ms = (long) (2000 * Math.random());
System.out.println("服务耗时:" + ms + " ms");
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用服务时间:" + time + ",服务耗时:" + ms + " ms");
return "调用服务时间:" + time + " -> 未超时,服务正常";
}
}
server:
#服务端口,浏览器默认端口,地址栏可不用显示输入该端口号
port: 80
spring:
#服务名称
application:
name: zuul
eureka:
client:
service-url:
#服务注册地址
defaultZone: http://localhost:9000/eureka/
package com.ming.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy //开启zuul网关(默认引入断路机制)
public class ClientZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ClientZuulApplication.class, args);
}
}
client-user微服务要启动两个实例,来测试Zuul的负载均衡功能,配置运行环境如下:
依次点击ServerEurekaApplication、ClientUserApplication1、ClientUserApplication2、ClientZuulApplication,工程都启动成功后。在浏览器地址栏访问 http://localhost:9000,其结果如下(四个微服务实例启动正常):
在浏览器地址栏访问 http://localhost/user/timeout, 其结果如下:
在 http://localhost/user/timeout 中,localhost代表的是请求Zuul服务,因为采用的是默认的80端口,所以浏览器地址可以不给出端口号,user代表的是client-user微服务的serviceId (application.yml中的spring.application.name属性值),而timeout是请求路径,这样就会将请求转发到client-user微服务。
server:
#服务端口,浏览器默认端口,地址栏可不用显示输入
port: 80
spring:
#服务名称
application:
name: zuul
zuul:
routes:
user-service:
#配置client-user微服务请求路径
path: /u/**
#指定转发地址,zuul将请求转发到client-user微服务上,由于给定了服务端口号,无法实现负载均衡
#url: http://localhost:7001/
#指定服务ID,zuul将请求转发到client-user微服务上,自动使用服务端负载均衡,分摊请求
serviceId: user
eureka:
client:
service-url:
#服务注册地址
defaultZone: http://localhost:9000/eureka/
package com.ming.zuul.service;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@Service
public class ZuulFallBackService implements FallbackProvider {
@Override
public String getRoute() {
//client-user微服务serviceId(spring.application.name)
return "user";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
//设置请求头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
@Override
public InputStream getBody() {
//设置请求体
return new ByteArrayInputStream("超时,服务异常".getBytes());
}
@Override
public HttpStatus getStatusCode() {
//设置响应状态
return HttpStatus.REQUEST_TIMEOUT;
}
@Override
public int getRawStatusCode() {
//设置响应状态码
return HttpStatus.REQUEST_TIMEOUT.value();
}
@Override
public String getStatusText() {
//设置响应状态信息
return HttpStatus.REQUEST_TIMEOUT.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
依次点击ServerEurekaApplication、ClientUserApplication1、ClientUserApplication2、ClientZuulApplication,工程都启动成功后。在浏览器地址栏访问 http://localhost:9000,其结果如下(四个微服务实例启动正常):
在浏览器地址栏访问 http://u/timeout, 其结果如下:
然后查看控制台ClientUserApplication1,结果如下:
默认响应时长超过1000ms,启用熔断机制,进行降级服务。然而每个请求默认有1次重试机会,查看控制台ClientUserApplication2,如下:
第二次重新请求,响应未超时,微服务被正常调用。从上面图中可以看到,两次请求的client-zuul服务端口号分别为7002、7001,Zuul默认采用轮询机制实现负载均衡。
再次在浏览器地址栏访问 http://u/timeout, 其结果如下:
查看控制台ClientUserApplication1、ClientUserApplication2,结果如下:
两次请求均超时,启用熔断机制,进行降级服务。
访问 http://localhost/u/timeout?serialId=521&authCode=123, 当serialId与authCode一致时,Zuul转发请求到client-user微服务;访问 http://localhost/u/timeout?serialId=521&authCode=521, 当serialId与authCode不一致时,不再转发到微服务,浏览器页面显示 " 服务认证失败!"。
package com.ming.zuul.service;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Service
public class ZuulFilterService extends ZuulFilter {
@Override
public String filterType() {
//设置前置过滤
return "pre";
}
@Override
public int filterOrder() {
//设置过滤器顺序,数字越小,优先级越高
return 0;
}
@Override
public boolean shouldFilter() {
//获取当前请求上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//获取HttpServletRequest对象
HttpServletRequest request = currentContext.getRequest();
//获取请求参数序列Id
String serialId = request.getParameter("serialId");
//如果存在序列Id,则启用过滤器
return !StringUtils.isEmpty(serialId);
}
@Override
public Object run() {
//获取当前请求上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//获取HttpServletRequest对象
HttpServletRequest request = currentContext.getRequest();
//获取请求参数序列Id
String serialId = request.getParameter("serialId");
//获取请求参数认证码
String authCode = request.getParameter("authCode");
if (!serialId.equals(authCode)) {
//设置不在转发请求
currentContext.setSendZuulResponse(false);
//设置HTTP状态响应码
currentContext.setResponseStatusCode(401);
//设置响应编码,防止出现响应页面乱码
currentContext.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//设置响应体
currentContext.setResponseBody("服务认证失败!");
}
return null;
}
}
依次点击ServerEurekaApplication、ClientUserApplication1、ClientUserApplication2、ClientZuulApplication,工程都启动成功后。在浏览器地址栏访问 http://localhost:9000,其结果如下(四个微服务实例启动正常):
在浏览器地址栏访问 http://localhost/u/timeout?serialId=521&authCode=123, 其结果如下:
在浏览器地址栏访问 http://localhost/u/timeout?serialId=521&authCode=521, 其结果如下: