一,在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简答的微服务系统如下图:
在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。
二,Zuul简介:
由于微服务过多,可能某一个小业务就需要调各种微服务的接口,不可避免的就会需要负载均衡和反向代理了,以确保ui不直接与所有的微服务接口接触,所以我们需要使用一个组件来做分发,跨域等各种请求。
ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,它主要用作反向代理、Filter扩展、动态加载、动态路由、压力测试、弹性扩展、审查监控、安全检查等。
通过网关的方式,提供致对外的服务,具体的服务调用分发由网关根据注册中心进行分发。
三,继续使用上一节的工程。在原有的工程上,创建一个新的工程。
3.1 可以在创建service-zuul模块时勾选依赖:勾选 Eureka Discovery Client ,Zuul和Web或者也可以走默认,然后在pom.xml文件下添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zhanghq</groupId>
<artifactId>service_zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service_zuul</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 在其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能:
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class, args);
}
}
3.3 加上配置文件application.yml加上以下的配置代码:
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8765
spring:
application:
name: service-zuul
#第二种方式配置zuul地址映射(服务ID)
#zuul:
# routes:
# api-a:
# path: /api-a/**
# service-id: service-consumer-feign
# api-b:
# path: /api-b/**
# service-id: service-consumer-ribbon
#第一种方式配置url映射
#zuul:
# routes:
# feign:
# path: /feign/**
# url: http://localhost:8085/
#第三中方式zuul.routes.服务名称: 访问路径
zuul:
routes:
consumer-feign: /feign/**
consumer-ribbon: /ribbon/**
#若忽略某个微服务不走网关,则这样设置:
#ignored-services: service-consumer-ribbon
#路由前缀
#prefix: /zuul
#默认不生效,要想生效,zuul.stripPrefix=false 把忽略前缀设置成false
#strip-prefix: false
先指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8765,服务名为service-zuul;以/feign/ 开头的请求都转发给consumer-ribbon服务;以/ribbon/开头的请求都转发给consumer-feign服务;
3.4 依次运行这四个工程;打开浏览器访问:http://localhost:8765/feign/hi?name=jackson ;浏览器显示:
四,服务过滤
4.1 zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程;
@Component
public class AccessFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
/**
* pre:可以在请求被路由之前调用
* route:在路由请求时候被调用
* post:在route和error过滤器之后被调用
* error:处理请求时发生错误时被调用
* @return
*/
@Override
public String filterType() {
return "pre"; //请求前被调用;
}
/*
filterOrder:过滤的顺序
shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
*/
@Override
public int filterOrder() {
return 0; //越小越先执行
}
/**
* 是否执行过滤器,过滤器是否执行的开关
* 如果有多个过滤器时,上一个过滤器返回为true,下一个过滤器需要执行,如果为false就没必要执行了
*
*/
@Override
public boolean shouldFilter() {
return true;//此过滤器始终执行
// return (boolean)RequestContext.getCurrentContext().get("isSuccess");
}
/**
* 具体执行的逻辑
* @return
* @throws ZuulException
*/
@Override
@ResponseBody
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest(); //获取request
System.out.println(String.format("%s AccessPasswordFilter request to %s", request.getMethod(), request.getRequestURL().toString()));
String author = request.getHeader("author"); //得到请求头的用户信息
//author = "jackson"; //添加一个临时数据测试
if(StringUtils.isNotEmpty(author)){ //假设的逻辑
requestContext.setSendZuulResponse(true); //对该请求进行路由
requestContext.setResponseStatusCode(HttpStatus.OK.value());
requestContext.set("isSuccess",true); //设置一个状态,下一个过滤器可能会用到的
return null;
}else {
requestContext.setSendZuulResponse(false); //过滤该请求,不进行路由
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
Map json = new HashMap();
json.put("msg","用户令牌错误");
requestContext.setResponseBody(json.toString()); //返回前端内容
HttpServletResponse response = requestContext.getResponse();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
requestContext.set("isSuccess",false);
return null;
}
}
}
4.2 通过实现接口可以实现Zuul的容错与回退功能,下面这个例子来自Zuul的源码DefaultFallbackProvider,这里我稍微修改了下:
@Component
public class ZuulFallBack implements FallbackProvider {
@Override
public String getRoute() {
return "consumer-feign"; //匹配微服务名字,可以用*或者null 代表所有服务的都过滤
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;//请求网关成功了,所以是ok
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
@ResponseBody
public InputStream getBody() throws IOException {
Map json=new HashMap();
json.put("state","501");
json.put("msg","后台接口错误");
return new ByteArrayInputStream(json.toString().getBytes("UTF-8")); //返回前端的内容
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
return httpHeaders;
}
};
}
}
上一篇:Spring cloud学习之路(五,断路器 Hystrix)https://blog.csdn.net/Zhang_Jackson/article/details/103293354
下一篇: