调用统计与API网关
# 调用统计与 API 网关
# 第四阶段
# 主要内容
- 开发接口调用次数统计功能
- 优化整个系统的架构 —— API 网关详解
- 网关是什么?
- 网关的作用?
- 网关的应用场景及实现?
- 结合业务去应用网关
# 接口调用次数统计
需求:
- 用户每次调用接口成功,次数 +1(可以考虑用 -1)
- 给用户分配或者用户自主申请接口调用次数
业务流程:
- 用户调用接口(之前已完成)
- 修改数据库,调用次数 +1
设计库表:
用哪个用户?哪个接口?
用户 => 接口(多对多)
用户调用接口关系表:
-- 用户调用接口关系表
create table if not exists cmapi.`user_interface_info`
(
`id` bigint not null auto_increment comment '主键' primary key,
`userId` bigint not null comment '调用用户 id',
`interfaceInfoId` bigint not null comment '接口 id',
`totalNum` int default 0 not null comment '总调用次数',
`leftNum` int default 0 not null comment '剩余调用次数',
`status` int default 0 not null comment '0-正常,1-禁用',
`createTime` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`updateTime` datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
`isDelete` tinyint default 0 not null comment '是否删除(0-未删,1-已删)'
) comment '用户调用接口关系';
2
3
4
5
6
7
8
9
10
11
12
13
开发步骤:
- 开发基本增删改查(给管理员用)
- 开发用户调用接口次数 +1 的功能(service)
# 问题
如果每个接口的方法都调用次数 +1,是不是比较麻烦?
致命问题: 接口开发者需要自己去添加统计代码。

- 使用 AOP 切面的优点: 独立于接口,在每个接口调用后统计次数 +1
- 使用 AOP 切面的缺点: 只存在于单个项目中,如果每个团队都要开发自己模拟接口,那么都要写一个切面
# 网关
什么是网关?理解成火车站的检票口,统一去检票。
# 网关作用
统一去进行一些操作、处理一些问题。比如:
- 路由
- 负载均衡
- 统一鉴权
- 跨域
- 统一业务处理(缓存也在这里处理)
- 访问控制
- 发布控制
- 流量染色
- 接口保护
- 限制请求
- 信息脱敏(操作请求头,响应头)
- 降级(熔断)—— 请求某个接口时不成功,提示请求其它接口,兜底效果
- 限流:学习令牌桶算法、学习漏桶算法,学习一下 RedisLimitHandler
- 超时时间
- 统一日志
- 统一文档
# 路由
起到转发的作用,比如有接口 A 和接口 B,网关会记录这些信息,根据用户访问的地址和参数,转发请求到对应的接口(服务器/集群)。
/a => 接口 A
/b => 接口 B
2
参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
# 负载均衡
在路由的基础上:
/c => 服务A/集群A(随机转发到其中的某一机器)
uri 从固定地址改成 lb:xxxx。
# 统一鉴权
判断用户是否有权限进行操作,无论访问什么接口,都统一去判断权限,不用重复写。
# 统一处理跨域
网关统一处理跨域,不用在每个项目里单独处理。
参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
# 统一业务处理
把每个项目中都要做的通用逻辑放到上层(网关),统一处理,比如本项目的次数统计。
# 访问控制
黑白名单,比如限制 DDOS IP。
# 发布控制
灰度发布,比如上线新接口,先给新接口分配 20% 的流量,老接口 80%,再慢慢调整比重。(不同人的软件最新版不一样)
参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-weight-route-predicate-factory
# 流量染色
给请求(流量)添加一些标识,一般是设置请求头中,添加新的请求头。
- 添加请求头:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-addrequestheader-gatewayfilter-factory
- 全局染色:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#default-filters
# 统一接口保护
- 限制请求:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#requestheadersiz-gatewayfilter-factory
- 信息脱敏:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-removerequestheader-gatewayfilter-factory
- 降级(熔断) 进行兜底:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#fallback-headers
- 限流:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-requestratelimiter-gatewayfilter-factory
- 超时时间 超时就中断:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#http-timeouts-configuration
- 重试(业务保护):https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-retry-gatewayfilter-factory
# 统一日志
统一的请求、响应信息记录。
# 统一文档
将下游项目的文档进行聚合,在一个页面统一查看。
建议用:https://doc.xiaominfo.com/docs/middleware-sources/aggregation-introduction
# 网关的分类
- 全局网关(接入层网关):作用是负载均衡、请求日志等,不和业务逻辑绑定
- 业务网关(微服务网关):会有一些业务逻辑,作用是将请求转发到不同的业务/项目/接口/服务
参考文章:https://blog.csdn.net/qq_21040559/article/details/122961395
# 网关实现
- Nginx(全局网关)、Kong 网关(API 网关),编程成本相对较高
- Spring Cloud Gateway(取代了 Zuul),性能高,可以用 Java 代码来写逻辑,适于学习
网关技术选型:https://zhuanlan.zhihu.com/p/500587132
# Spring Cloud Gateway 用法
全部内容基本来自官网:
- 官网:https://spring.io/projects/spring-cloud-gateway
- 官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
# 核心概念
| 概念 | 说明 |
|---|---|
| 路由 Route | 根据什么条件,转发请求到哪里 |
| 断言 Predicate | 一组规则、条件,用来确定如何转发路由 |
| 过滤器 Filter | 对请求进行一系列的处理,比如添加请求头、添加请求参数 |
请求流程:
- 客户端发起请求
- Handler Mapping:根据断言,去将请求转发到对应的路由
- Web Handler:处理请求(一层层经过过滤器)
- 实际调用服务

# 两种配置方式
1. 配置式(声明式,方便、规范)
简化版:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
2
3
4
5
6
7
8
完整版:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
2
3
4
5
6
7
8
9
10
11
2. 编程式(灵活、相对麻烦)
# 建议开启日志
logging:
level:
org:
springframework:
cloud:
gateway: trace
2
3
4
5
6
# 断言
官网地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
目录:
- After —— 在 xx 时间之后
- Before —— 在 xx 时间之前
- Between —— 在 xx 时间之间
- 请求类别
- 请求头(包含 Cookie)
- 查询参数
- 客户端地址
- 权重
The After Route Predicate Factory
当前时间在这个时间之后,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
2
3
4
5
6
7
8
The Before Route Predicate Factory
当前时间在这个时间之前,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
2
3
4
5
6
7
8
The Between Route Predicate Factory
当前时间在这个时间之间,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
2
3
4
5
6
7
8
The Cookie Route Predicate Factory
如果你的请求头 cookie 的是 chocolate,它的值是 ch.p,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
2
3
4
5
6
7
8
The Header Route Predicate Factory
如果你的请求头包含 X-Request-Id 这样一个请求头,并且它的值符合正则表达式的规则,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
2
3
4
5
6
7
8
The Host Route Predicate Factory
如果你的访问的是这个 **.somehost.org, **.anotherhost.org 域名,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
2
3
4
5
6
7
8
The Method Route Predicate Factory
如果你的请求类别是 GET、POST,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
2
3
4
5
6
7
8
The Path Route Predicate Factory
如果你的访问的地址是以 /red/{segment}, /blue/{segment} 路径作为前缀,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
2
3
4
5
6
7
8
The Query Route Predicate Factory
根据查询条件,比如 red 匹配 gree.,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
2
3
4
5
6
7
8
The RemoteAddr Route Predicate Factory
根据远程地址,比如你的用户的 ip 地址是 192.168.1.1/24,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
2
3
4
5
6
7
8
The Weight Route Predicate Factory
根据你设置的权重,给你把同一个访问的地址,重定向到不同的服务,轻松实现发布控制。
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
2
3
4
5
6
7
8
9
10
11
12
The XForwarded Remote Addr Route Predicate Factory
从请求头中如果拿到 XForwarded 这个请求头的地址 192.168.1.1/24,就会访问当前这个路由。
spring:
cloud:
gateway:
routes:
- id: xforwarded_remoteaddr_route
uri: https://example.org
predicates:
- XForwardedRemoteAddr=192.168.1.1/24
2
3
4
5
6
7
8
# 过滤器
官网文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
基本功能:对请求头、请求参数、响应头的增删改查。
- 添加请求头
- 添加请求参数
- 添加响应头
- 降级
- 限流
- 重试
引入依赖(用于集成 Reactor Resilience4j 断路器库,以实现微服务的容错处理和服务熔断(接口保护 —— 降级)等功能):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
2
3
4
小作业:
通过阅读源码:https://spring.io/projects/spring-cloud-gateway/#samples 来了解 gateway 编程式开发。
# 第五阶段
# 主要内容
- 实现统一的接口鉴权和计费(API 网关实践)
实现统一的用户鉴权,统一的接口调用次数统计(把 API 网关用到项目中)。
# 要用到的特性
- 路由(转发请求到模拟接口项目)
负载均衡(需要用到注册中心)- 统一鉴权(
accessKey、secretKey) - 统一处理跨域
- 统一业务处理(每次请求接口后,接口调用次数 +1)
- 访问控制(黑白名单)
发布控制- 流量染色(记录请求是否为网关来的)
统一接口保护- 限制请求
- 信息脱敏
- 降级(熔断)
- 限流(学习令牌桶算法、学习漏桶算法,学习一下 RedisLimitHandler)
- 超时时间
- 重试(业务保护)
- 统一日志(记录每次的请求和响应)
统一文档
# 业务逻辑
- 用户发送请求到 API 网关
面试官可能会问:为什么要用 API 网关?可以联想一下上面那个画图过程。
- 请求日志
- (黑白名单)
- 用户鉴权(判断 ak、sk 是否合法)
- 请求的模拟接口是否存在?
- 请求转发,调用模拟接口
- 响应日志
- 调用成功,接口调用次数 +1
- 调用失败,返回一个规范的错误码
# 具体实现
# 1、请求转发
使用 Path 前缀匹配断言:
将所有前缀为 /api/ 的请求进行转发,转发到 http://localhost:8123/api。
比如请求网关 localhost:8090/api/name/张三,转发到 localhost:8123/api/name/张三。
server:
port: 8090
spring:
cloud:
gateway:
routes:
- id: api_route
# 设置转发到哪个网址
uri: http://localhost:8123
# 前缀断言
predicates:
- Path=/api/**
2
3
4
5
6
7
8
9
10
11
12
13
# 2、编写业务逻辑
使用了 GlobalFilter(编程式,还有一种叫配置式,上面有讲),全局请求拦截处理(类似 AOP)。
官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters
因为网关项目没引入 MyBatis 等操作数据库的类库,如果该操作较为复杂,可以由 backend 增删改查项目提供接口,我们直接调用,不用重复写逻辑了。
- HTTP 请求(用 HTTPClient、用 RestTemplate、Feign)
- RPC(Dubbo)
# 问题
预期等模拟接口调用完成,才记录响应日志、统计调用次数。
但现实是 chain.filter 方法立刻返回了,直到 filter 过滤器 return 后(此时已经记录了响应日志)才调用了模拟接口。
原因是: chain.filter 是个异步操作,理解为前端的 promise。
解决方案: 利用 response 装饰者,增强原有 response 的处理能力。
参考博客:https://blog.csdn.net/qq_19636353/article/details/126759522(以这个为主)
其他参考: