在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简单的微服务系统如下图:
在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。
Zuul简介
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。
创建Ahut-Service-Zuul项目
新创建一个工程Ahut-Service-Zuul,创建过程和Ahut-Eureka-Client-One一样
项目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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ahut</groupId>
<artifactId>service-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-zuul</name>
<description>service zuul</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-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>
在项目启动类加上注解@EnableZuulProxy,开启zuul的功能:
package com.ahut;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author cheng
* @className: ServiceZuulApplication
* @description: zuul proxy
* @dateTime 2018/4/27 14:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class, args);
}
}
配置文件application.properties
# 配置服务器端口
server.port=8950
# spring项目名称
spring.application.name=AHUT-SERVICE-ZUUL
# 注册中心url
eureka.client.service-url.defaultZone=http://localhost:8900/eureka/
# 路由网关
# /api-ribbon/**请求路径的转发给AHUT-SERVICE-RIBBON服务
zuul.routes.api-ribbon.path=/api-ribbon/**
zuul.routes.api-ribbon.serviceId=AHUT-SERVICE-RIBBON
# /api-feign/**请求路径的转发给AHUT-SERVICE-FEIGN服务
zuul.routes.api-feign.path=/api-feign/**
zuul.routes.api-feign.serviceId=AHUT-SERVICE-FEIGN
说明:
- 以/api-ribbon/ 开头的请求都转发给AHUT-SERVICE-RIBBON服务
- 以/api-feign/开头的请求都转发给AHUT-SERVICE-FEIGN服务
开启项目:
- Ahut-Eureka-Server(注册中心)
- Ahut-Eureka-Client-One(AHUT-EUREKA-CLIENT服务一)
- Ahut-Service-Feign(AHUT-SERVICE-FEIGN服务消费者)
- Ahut-Service-Ribbon(AHUT-SERVICE-RIBBON服务消费者)
- Ahut-Service-Zuul(AHUT-SERVICE-ZUUL路由网关)
访问eureka:
访问 http://localhost:8950/api-ribbon/v1/hello
请求转发到AHUT-SERVICE-RIBBON服务
访问 http://localhost:8950/api-feign/v1/hello
请求转发到AHUT-SERVICE-FEIGN服务
说明zuul路由网关已经生效
此时的项目架构
服务过滤
zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程。
新建过滤器
package com.ahut.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Writer;
/**
* @author cheng
* @className: MyFilter
* @description: 自定义过滤器
* @dateTime 2018/4/27 15:02
*/
@Component
public class MyFilter extends ZuulFilter {
/**
* @description: 返回一个字符串代表过滤器的类型
* @author cheng
* @dateTime 2018/4/27 15:20
*/
@Override
public String filterType() {
return "pre";
}
/**
* @description: 过滤的顺序
* @author cheng
* @dateTime 2018/4/27 15:22
*/
@Override
public int filterOrder() {
return 0;
}
/**
* @description: 这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
* @author cheng
* @dateTime 2018/4/27 15:22
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* @description: 过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
* @author cheng
* @dateTime 2018/4/27 15:22
*/
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
// 获取request
HttpServletRequest request = context.getRequest();
// 获取token
String token = request.getParameter("token");
if (token == null) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
Writer writer = null;
try {
// 获取输出流
writer = context.getResponse().getWriter();
writer.write("token is empty");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
// 关闭流
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return null;
}
}
方法说明:
- filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
pre:路由之前
routing:路由之时
post: 路由之后
error:发送错误调用 - filterOrder:过滤的顺序
- shouldFilter:这里可以写逻辑判断,是否要过滤,本文true表示永远过滤
- run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问
不带token访问 http://localhost:8950/api-ribbon/v1/hello
带token访问 http://localhost:8950/api-feign/v1/hello?token=token
Zuul的服务过滤起作用