目录
1 微服务网关概述
1.1 服务网关的概念
1.1.1 什么是微服务网关
1.1.2 作用和应用场景
1.2 常见的API网关实现方式
1.3 基于Nginx的网关实现
1.3.1 Nginx介绍
1.3.3 准备工作
1.3.4 配置Nginx的请求转发
1.3.5 访问nginx
2 微服务网关Zuul
2.1 Zuul简介
2.2 搭建Zuul网关服务器
2.3 Zuul中的路由转发
2.3.1 面向服务的路由
2.3.2 简化的路由配置
2.3.3 默认的路由规则
2.3.4 Zuul加入后的架构
2.4 Zuul中的过滤器
2.4.1 ZuulFilter简介
2.4.2 生命周期
2.4.3 自定义过滤器
2.5 服务网关Zuul的核心源码解析
2.6 Zuul网关存在的问题
3 微服务网关GateWay
3.1 Gateway简介
3.1.1 简介
3.1.2 核心概念
3.2 入门案例
3.2.1 入门案例
3.2.2 路由规则
实例:
3.2.3 动态路由
3.2.4 重写转发路径
3.3 过滤器
3.3.1 过滤器基础
3.3.2 局部过滤器
3.3.3 全局过滤器
3.4 统一鉴权
3.4.1 鉴权逻辑
3.4.2 代码实现
3.5 网关限流
3.5.2 基于Filter的限流
3.5.3 基于Sentinel的限流
3.6 网关高可用
3.7 执行流程分析
4 微服务的链路追踪概述
4.1 微服务架构下的问题
4.2 Sleuth概述
4.2.1 简介
4.2.2 相关概念
4.3 链路追踪Sleuth入门
4.4 Zipkin的概述
4.5 Zipkin Server的部署和配置
4.6 客户端Zipkin+Sleuth整合
4.7 基于消息中间件收集数据
4.7.1 RabbitMQ的安装与启动
4.8 存储跟踪数据
4.8.1 准备数据库
因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可,这样简化了开发还有以下优点:
在\nginx-1.8.0\conf\nginx.conf中配置
location /api-order {
proxy_pass http://127.0.0.1:9001/;
}
location /api-product {
proxy_pass http://127.0.0.1:9002/;
}
localhost:80
org.springframework.cloud
spring-cloud-starter-netflix-zuul
@SpringBootApplication
//开启zuul网关功能
@EnableZuulProxy
//eureka的服务发现
@EnableDiscoveryClient
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class,args);
}
}
server:
port: 8080 #端口
spring:
application:
name: api-zuul-server #服务名称
zuul:
routes:
#已商品微服务
product-service: #路由id,随便写
path: /product-service/** #映射路径 #localhost:8080/product-service/sxxssds
url: http://127.0.0.1:9001 #映射路径对应的实际微服务url地址
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
@SpringBootApplication
//开启zuul网关功能
@EnableZuulProxy
//eureka的服务发现
@EnableDiscoveryClient
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class,args);
}
}
#配置Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
zuul:
routes:
#已商品微服务
product-service: #路由id,随便写
path: /product-service/** #映射路径 #localhost:8080/product-service/sxxssds
serviceId: service-product #配置转发的微服务的服务名称
zuul:
routes:
#已商品微服务
#如果路由id 和 对应的微服务的serviceId一致的话
service-product: /product-service/**
zuul:
routes:
#已商品微服务
#如果当前的微服务名称 service-product , 默认的请求映射路径 /service-product/**
# /service-order/
/**
* 自定义的zuul过滤器
* 继承抽象父类
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 定义过滤器类型
* pre
* routing
* post
* error
*/
public String filterType() {
return "pre";
}
/**
* 指定过滤器的执行顺序
* 返回值越小,执行顺序越高
*/
public int filterOrder() {
return 1;
}
/**
* 当前过滤器是否生效
* true : 使用此过滤器
* flase : 不使用此过滤器
*/
public boolean shouldFilter() {
return true;
}
/**
* 指定过滤器中的业务逻辑
*/
public Object run() throws ZuulException {
//System.out.println("执行了过滤器");
}
}
正常流程:
代码实现:
/**
* 自定义的zuul过滤器
* 继承抽象父类
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 定义过滤器类型
* pre
* routing
* post
* error
*/
public String filterType() {
return "pre";
}
/**
* 指定过滤器的执行顺序
* 返回值越小,执行顺序越高
*/
public int filterOrder() {
return 1;
}
/**
* 当前过滤器是否生效
* true : 使用此过滤器
* flase : 不使用此过滤器
*/
public boolean shouldFilter() {
return true;
}
/**
* 指定过滤器中的业务逻辑
* 身份认证:
* 1.所有的请求需要携带一个参数 : access-token
* 2.获取request请求
* 3.通过request获取参数access-token
* 4.判断token是否为空
* 4.1 token==null : 身份验证失败
* 4.2 token!=null : 执行后续操作
* 在zuul网关中,通过RequestContext的上下问对象,可以获取对象request对象
*/
public Object run() throws ZuulException {
//System.out.println("执行了过滤器");
//1.获取zuul提供的上下文对象RequestContext
RequestContext ctx = RequestContext.getCurrentContext();
//2.从RequestContext中获取request
HttpServletRequest request = ctx.getRequest();
//3.获取请求参数access-token
String token = request.getParameter("access-token");
//4.判断
if (token ==null) {
//4.1 如果token==null ,拦截请求,返回认证失败
ctx.setSendZuulResponse(false); // 拦截请求
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
//4.2 如果token!=null ,继续后续操作
return null;
}
}
org.springframework.cloud
spring-cloud-starter-gateway
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class,args);
}
}
cloud: #配置SpringCloudGateway的路由
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/product/**
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
#eureka注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
cloud: #配置SpringCloudGateway的路由
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/product/**
cloud: #配置SpringCloudGateway的路由
gateway:
routes:
- id: order-service
uri: lb://service-order
predicates:
- Path=/order-service/**
filters:
- RewritePath=/order-service/(?.*), /$\{segment}
(1)过滤器的生命周期
/**
* 自定义一个全局过滤器
* 实现 globalfilter , ordered接口
*/
//@Component
public class LoginFilter implements GlobalFilter,Ordered {
/**
* 执行过滤器中的业务逻辑
* 对请求参数中的access-token进行判断
* 如果存在此参数:代表已经认证成功
* 如果不存在此参数 : 认证失败.
* ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
*/
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
//1.获取请求参数access-token
String token = exchange.getRequest().getQueryParams().getFirst("access-token");
//2.判断是否存在
if(token == null) {
//3.如果不存在 : 认证失败
System.out.println("没有登录");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); //请求结束
}
//4.如果存在,继续执行
return chain.filter(exchange); //继续向下执行
}
/**
* 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
*/
public int getOrder() {
return 0;
}
}
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-data-redis-reactive
server:
port: 8080 #端口
spring:
application:
name: api-gateway-server #服务名称
redis:
host: localhost
pool: 6379
database: 0
cloud: #配置SpringCloudGateway的路由
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/product-service/**
filters:
# - name: RequestRateLimiter
# args:
# # 使用SpEL从容器中获取对象 即限流规则
# key-resolver: '#{@userKeyResolver}'
# # 令牌桶每秒填充平均速率
# redis-rate-limiter.replenishRate: 1
# # 令牌桶的上限
# redis-rate-limiter.burstCapacity: 3
- RewritePath=/product-service/(?.*), /$\{segment}
@Configuration
public class KeyResolverConfiguration {
/**
* 编写基于请求路径的限流规则
* //abc
* //基于请求ip 127.0.0.1
* //基于参数
*/
//@Bean
public KeyResolver pathKeyResolver() {
//自定义的KeyResolver
return new KeyResolver() {
/**
* ServerWebExchange :
* 上下文参数
*/
public Mono resolve(ServerWebExchange exchange) {
return Mono.just( exchange.getRequest().getPath().toString());
}
};
}
/**
* 基于请求参数的限流
*
* 请求 abc ? userId=1
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getQueryParams().getFirst("userId")
//exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
);
}
}
com.alibaba.csp
sentinel-spring-cloud-gateway-adapter
1.6.3
/**
* sentinel限流的配置
*/
//@Configuration
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
* 用于指定资源的限流规则.
* 1.资源名称 (路由id)
* 2.配置统计时间
* 3.配置限流阈值
*/
@PostConstruct
public void initGatewayRules() {
Set rules = new HashSet<>();
// rules.add(new GatewayFlowRule("product-service")
// .setCount(1)
// .setIntervalSec(1)
// );
rules.add(new GatewayFlowRule("product_api")
.setCount(1).setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
}
/**
* 自定义限流处理器
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockHandler = new BlockRequestHandler() {
public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",001);
map.put("message","不好意思,限流啦");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockHandler);
}
* 配置初始化的限流参数
* 用于指定资源的限流规则.
* 1.资源名称 (路由id)
* 2.配置统计时间
* 3.配置限流阈值
*/
@PostConstruct
public void initGatewayRules() {
Set rules = new HashSet<>();
rules.add(new GatewayFlowRule("product-service")
.setCount(1)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("product_api")
.setCount(1).setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
/**
* 自定义API限流分组
* 1.定义分组
* 2.对小组配置限流规则
*/
@PostConstruct
private void initCustomizedApis() {
Set definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/product-service/product/**"). //已/product-service/product/开都的所有url
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/order-service/order")); //完全匹配/order-service/order 的url
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
org.springframework.cloud
spring-cloud-starter-sleuth
logging:
level:
root: info
org.springframework.web.servlet.DispatcherServlet: DEBUG
org.springframework.cloud.sleuth: DEBUG
org.springframework.cloud
spring-cloud-starter-zipkin
#修改zipkin使用rabbitmq采集数据
zipkin:
#base-url: http://127.0.0.1:9411/ #server的请求地址
sender:
#type: web #数据的传输方式 , 已http的形式向server端发送数据
type: rabbit #向rabbitmq中发送消息
sleuth:
sampler:
probability: 1 #采样比
org.springframework.cloud
spring-cloud-sleuth-zipkin
org.springframework.amqp
spring-rabbit
zipkin:
sender:
#数据的传输方式 , 已http的形式向server端发送数据
type: rabbit #向rabbitmq中发送消息
sleuth:
sampler:
probability: 1 #采样比
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
listener: # 这里配置了重试策略
direct:
retry:
enabled: true
simple:
retry:
enabled: true
/*
SQLyog Ultimate v11.33 (64 bit)
MySQL - 5.5.58 : Database - zipkin
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`zipkin` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `zipkin`;
/*Table structure for table `zipkin_annotations` */
DROP TABLE IF EXISTS `zipkin_annotations`;
CREATE TABLE `zipkin_annotations` (
`trace_id_high` bigint(20) NOT NULL DEFAULT '0' COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` varchar(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` blob COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` int(11) NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` bigint(20) DEFAULT NULL COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` int(11) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` binary(16) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` smallint(6) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` varchar(255) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
UNIQUE KEY `trace_id_high` (`trace_id_high`,`trace_id`,`span_id`,`a_key`,`a_timestamp`) COMMENT 'Ignore insert on duplicate',
KEY `trace_id_high_2` (`trace_id_high`,`trace_id`,`span_id`) COMMENT 'for joining with zipkin_spans',
KEY `trace_id_high_3` (`trace_id_high`,`trace_id`) COMMENT 'for getTraces/ByIds',
KEY `endpoint_service_name` (`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames',
KEY `a_type` (`a_type`) COMMENT 'for getTraces',
KEY `a_key` (`a_key`) COMMENT 'for getTraces',
KEY `trace_id` (`trace_id`,`span_id`,`a_key`) COMMENT 'for dependencies job'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
/*Data for the table `zipkin_annotations` */
/*Table structure for table `zipkin_dependencies` */
DROP TABLE IF EXISTS `zipkin_dependencies`;
CREATE TABLE `zipkin_dependencies` (
`day` date NOT NULL,
`parent` varchar(255) NOT NULL,
`child` varchar(255) NOT NULL,
`call_count` bigint(20) DEFAULT NULL,
UNIQUE KEY `day` (`day`,`parent`,`child`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
/*Data for the table `zipkin_dependencies` */
/*Table structure for table `zipkin_spans` */
DROP TABLE IF EXISTS `zipkin_spans`;
CREATE TABLE `zipkin_spans` (
`trace_id_high` bigint(20) NOT NULL DEFAULT '0' COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` bigint(20) NOT NULL,
`id` bigint(20) NOT NULL,
`name` varchar(255) NOT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`debug` bit(1) DEFAULT NULL,
`start_ts` bigint(20) DEFAULT NULL COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` bigint(20) DEFAULT NULL COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
UNIQUE KEY `trace_id_high` (`trace_id_high`,`trace_id`,`id`) COMMENT 'ignore insert on duplicate',
KEY `trace_id_high_2` (`trace_id_high`,`trace_id`,`id`) COMMENT 'for joining with zipkin_annotations',
KEY `trace_id_high_3` (`trace_id_high`,`trace_id`) COMMENT 'for getTracesByIds',
KEY `name` (`name`) COMMENT 'for getTraces and getSpanNames',
KEY `start_ts` (`start_ts`) COMMENT 'for getTraces ordering and range'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
/*Data for the table `zipkin_spans` */
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root