主要内容:
这个话题还得从接口说起。传统的三层架构的项目,所有业务都在一个工程里面,比如一个项目里面包含了用户管理业务代码,订单业务的代码,支付业务的代码,这三块仅仅是作为一个项目的三个不同的模块存在, 而分布式微服务项目,为了减轻模块之间的耦合程度,上述三个模块都将变成三个独立的项目,比如用户要购物,要下单,首先得获取用户信息,这时订单服务就需要调用用户服务的相关接口获取用户信息( 这个过程一般是RPC调用 ,也就是接口是在RPC调用中产生的),或者有时候有别的公司要调用这个微服务系统中的接口,等等类似这样不同项目之间的接口调用,不能简简单单的直接去调用,考虑到数据的安全性,幂等性,接口的权限,我们需要在接口请求被处理之前,对这个接口请求的有效性做一个校验。有人说了,这个校验可以写在过滤器里面,没错,但是如果有十个项目都调用同一个接口,岂不是校验逻辑要重复写10遍?这种情况下,一个叫做微服务网关的东西出现了。 一般情况下,微服务项目都是存在于同一个内网系统中,比如,一个微服务系统,包含了用户,订单,支付项目,外面来的接口想要调用这三个项目的接口,都先得关在“小黑屋”经过一番验证,验证过了,才能去调用接口。这个小黑屋,就是网关。
开放接口:
第三方机构(如微信开放平台) 的接口要被调用(必须在外网访问) ,需要通过appid + appsecret 生成accessToken进行通讯,类似这样对外开放,用于被其他外部接口接口调用的接口就是开放接口。
内部接口:
只能在局域网中访问的接口,服务与服务之间都在同一个微服务系统中,保证系统安全
相对来说Nginx功能比Zuul功能更加强大,能够整合其他语言比如lua脚本实现强大的功能,同时Nginx可以更好的抗高并发,Zuul网关适用于请求过滤和拦截等。
相同点:Zuul和Nginx都可以实现负载均衡,反向代理,过滤请求,实现网关效果
不同点:
(1)开发语言:Nginx采用C语言编写,效率更高,zuul采用Java编写,
(2)负载均衡实现:
zuul的负载均衡实现采用ribbon+eureka实现负载本地负载均衡
Nginx的负载均衡是服务器端实现负载均衡
(3) Nginx比Zuul功能会更强大,Nginx可以整合一些脚本语言如Lua,Nginx适合于服务器端负载均衡,反向代理;
Zuul适合微服务中实现网关,对微服务实现网关拦截,而且使用技术是Java语言 建议使用nginx+zuul实现网关 (nginx+zuul 可以实现网关集群)
过滤器拦截单个tomcat服务器的请求,网关是拦截整个微服务系统的所有请求
网关分内网网关和外网网关,外网网关是专门针对于开放平台接口的,比如一些合作平台要调用接口时,就通过外网网关进行调用,内网网关是整个微服务内部之间接口相互调用的网关。
Netflix Zuul,zuul是spring cloud的一个推荐组件,https://github.com/Netflix/zuul
搭建一个项目springcloud2.0-zuul-apigateway
<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.0modelVersion>
<groupId>com.lchtestgroupId>
<artifactId>springcloud2.0-zuul-apigatewayartifactId>
<version>0.0.1-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.3.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
#注册中心
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8100/eureka/
#网关端口
server:
port: 80
#网关在注册中心的名称
spring:
application:
name: service-zuul
#配置网关反向代理(搭建zuul动态网关,下面这个配置放到git上(网关作为configclient从configserver读取git上的配置)然后开启所有监控中心接口)
zuul:
#定义转发服务的规则(路由规则)
routes:
api-a: # api-a这里可以随便命名
#以 /api-member/开头的接口请求转发到会员服务
path: /api-member/**
#serviceId是会员服务别名
serviceId: app-member
#问题:如果会员服务做了集群,如何转发? zuul网关默认整合了ribbon,自动实现负载均衡轮询效果!
api-b:
#客户端请求以127.0.0.1:80/api-order/开头的接口请求转发到订单服务
path: /api-order/**
#订单服务别名
serviceId: app-order
package com.lchtest;
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;
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class AppGateWay {
public static void main(String[] args) {
SpringApplication.run(AppGateWay.class, args);
}
}
启动eureka注册中心,网关服务,member服务,order服务,进行测试
直接访问服务: http://localhost:8001/ http://localhost:8005/
通过网关转发接口请求到服务:
http://localhost/api-member 以api-member开头的请求,全部被转发到member服务中了
http://localhost/api-order 以api-order开头的请求,全部被转发到order服务中了
修改member服务的配置文件中的端口号,再启动一个member服务,这时再通过网关去请求member接口
负载均衡效果如下:
zuul网关实现负载均衡的原理: zuul网关根据配置文件中定义的路由转发规则(如下),对/api-member/**这样的接口请求,去注册中心找注册名为 app-member的服务 ,获取这个服务的实际服务器地址信息,然后再由网关实现本地负载均衡,转发请求到真实的服务器。
zuul:
#定义转发服务的规则(路由规则)
routes:
api-a:
#以 /api-member/开头的接口请求转发到会员服务
path: /api-member/**
#serviceId是会员服务别名
serviceId: app-member
现在假设有这么一个场景,客户端访问member服务或者order服务时,要求校验token信息,如果不包含token,直接拒绝访问,如果保护token信息,就把请求转发到对应服务
package com.lchtest.filter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
/**
* 网关服务过滤器
* @author pc
* @Component注解,将@Component注入到spring容器
*/
@Component
public class TokenFilter extends ZuulFilter{
/**
* 过滤器类型
* pre:可以在请求被路由之前调用
routing:在路由请求时候被调用
post:在routing和error过滤器之后被调用
error:处理请求时发生错误时被调用
*/
private static final String FILTE_RTYPE = "pre";
/**
* 过滤器是否生效
*/
private static final boolean SHOULD_FILTER_EFFECTIVE = true;
/**
* 接口鉴权失败
*/
private static final int UNAUTHORIZED = 401;
/**
* 判断过滤器是否生效
*/
public boolean shouldFilter() {
return SHOULD_FILTER_EFFECTIVE;
}
/**
* 过滤器业务逻辑
*/
public Object run() throws ZuulException {
//拦截所有服务接口,判断服务接口上是否有传递userToken参数
//1.获取上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//2.获取request对象
HttpServletRequest request = currentContext.getRequest();
//3.获取token token一般都放在请求头里面发送请求,因此获取token也是从请求头里面获取
// String token = request.getHeader("token");
// 这里模拟从请求参数中获取token
String token = request.getParameter("userToken");
if(StringUtils.isEmpty(token)) {
// 不往下执行,由网关服务直接响应客户端
currentContext.setSendZuulResponse(false);
// 返回错误提示
currentContext.setResponseBody("userToken is missed.");
currentContext.setResponseStatusCode(UNAUTHORIZED);
return null;
}
// 接口鉴权通过,正常调用其他服务接口
return null;
}
/**
* 过滤器类型:
* pre:在请求处理之前执行
*/
@Override
public String filterType() {
return FILTE_RTYPE;
}
/**
* 过滤器的执行顺序(优先级)
* 一个请求在同一阶段存在多个过滤器的时候,存在过滤器的执行顺序
*/
@Override
public int filterOrder() {
return 0;
}
}
重启网关服务,通过网关访问order服务,不带userToken参数时,请求直接被网关拦截并返回401,带上token参数,能够正确访问到order服务
代码地址
使用到的项目:
springcloud2.0-zuul-apigateway
springcloud2.0-eureak-server(单注册中心)
springcloud2.0-feign-parent( order服务, member服务)