[TOC]
# **网关**
**api-gateway**
>api-gateway是一款轻量级、高性能、易扩展的基于zuul的网关产品,提供API的统一管理服务、涵盖API发布、管理、运维的全生命周期管理。对内辅助用户简单、快速、低成本、低风险的实现微服务聚合、前后端分离、系统集成等功能;对外面向合作伙伴、开发者开放服务。通过使用API-Gateway,我们能快速帮助用户实现传统ESB面临的主要场景,又能满足新型业务场景(移动应用等)所需的高性能、安全、可靠等要求。
![](https://img.kancloud.cn/31/6d/316d77a699b9df478e8741d809b4fa9b_650x287.png)
## 通用网关设计
![](https://img.kancloud.cn/1e/62/1e628e93dd065657ab37b7547fb36ebc_1013x680.png)
## 软负载ZUUL
![](https://img.kancloud.cn/7a/d3/7ad38b1114e34924cfe3b392383b894b_1032x574.png)
## 网关活动图
![](https://img.kancloud.cn/70/2d/702dba033e7bcad8632d6d39ba40742b_996x607.png)
## api-gateway在项目中的位置
![](https://img.kancloud.cn/fe/7a/fe7ac9108d57ec9d64e8d7d55fe5763b_1006x660.png)
## api gateway作用
* 简化客户端调用复杂度
在微服务架构模式下后端服务的实例数一般是动态的,对于客户端而言,很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。
![](https://img.kancloud.cn/be/30/be3023b48d1850fc3dc786eb6ef0a4e0_401x346.png)
* 数据裁剪以及聚合
通常而言不同的客户端在显示时对于数据的需求是不一致的,比如手机端或者Web端又或者在低延迟的网络环境或者高延迟的网络环境。因此为了优化客户端的使用体验,API Gateway可以对通用性的响应数据进行裁剪以适应不同客户端的使用需求,同时还可以将多个API调用逻辑进行聚合,从而减少客户端的请求数,优化客户端用户体验。
* 多渠道支持
当然我们还可以针对不同的渠道和客户端提供不同的API Gateway,对于该模式的使用由另外一个大家熟知的方式叫Backend for front-end,在Backend for front-end模式当中,我们可以针对不同的客户端分别创建其BFF。
![](https://img.kancloud.cn/37/7e/377e2cebc23913a3e3d136450f170545_688x399.png)
* 遗留系统的微服务改造
对于遗留系统而言进行微服务改造通常是由于原有的系统存在或多或少的问题,比如技术债务,代码质量,可维护性,可扩展性等等。API Gateway的模式同样适用于这一类遗留系统的改造,通过微服务化的改造逐步实现对原有系统中的问题的修复,从而提升对于原有业务相应力的提升。通过引入抽象层,逐步使用新的实现替换旧的实现。
![](https://img.kancloud.cn/0b/1a/0b1a6ddf8ef97aea17d8b96af9cb75c1_645x361.png)
在Spring Cloud体系中,Spring Cloud Zuul就是提供负载均衡,反向代理,权限认证的一个API Gateway。
# api-gateway代码分析
## 开启ZUUL
![](https://box.kancloud.cn/d0d1f3b21523120ba677a274168f44e7_1731x534.png)
## Zuul提供的功能
* 认证鉴权-可以识别访问资源的每一个请求,拒绝不满足的请求
* 监控埋点,跟踪有意义的数据并统计,以便生成有意义的生产视图
* 熔断限流,为每一个请求分配容量,并丢弃超过限制的请求
* 抗压设计,线程池隔离增大并发能力
## 网关的2层超时调优
![](https://img.kancloud.cn/39/37/3937fb3c071e84009e7e7e1d70f75cd1_1032x459.png)
### hystrix ribbon活动图
![](https://img.kancloud.cn/25/c3/25c3c3f9bf0595b85883cfe9777a3732_1218x594.png)
### hystrix ribbon配置
```
#设置最大容错超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 90000
#设置最大超时时间
ribbon:
eager-load:
enabled: true
ServerListRefreshInterval: 10 #刷新服务列表源的间隔时间
httpclient:
enabled: false
okhttp:
enabled: true
ReadTimeout: 90000
ConnectTimeout: 90000
OkToRetryOnAllOperations: true
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 1
```
## 源码分析
### zuul内部代码
![](https://img.kancloud.cn/00/73/0073419549fd2c3d08085234b5aedf1b_1350x782.png)
### 类图
![](https://img.kancloud.cn/de/dc/dedc89358d3836b8e5255e4136eaccfa_1324x754.png)
参考:https://www.jianshu.com/p/295e51bc1518
## zuul基于eureka的服务发现路由
#### 路由注册相关逻辑
org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator
![](https://box.kancloud.cn/f750026dbf93181e86d923c36858d5f3_1515x744.png)
启动时配置mapping等信息
![](https://img.kancloud.cn/d3/ac/d3ac95290ff1e494c9ae1fcc6f92eff3_1098x464.png)
#### zuul转发逻辑
以访问[http://127.0.0.1:9200/api-user/users-anon/login?username=admin](http://127.0.0.1:9200/api-user/users-anon/login?username=admin)为例
* 通过/api-user/users-anon/login查映射表
![](https://img.kancloud.cn/f9/59/f959d53038ee9054e5ce3886abe72d69_1701x794.png)
* 找到ZuulRoute映射关系
![](https://img.kancloud.cn/ca/52/ca52c0f7248f8e0dfb0d234b90c9e6df_1804x869.png)
* 执行pre过滤器org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter处理是否增加请求头等信息
![](https://img.kancloud.cn/ad/a9/ada9e7afbb5fbb5a296705684033fb85_1884x600.png)
* 执行route过滤器
![](https://img.kancloud.cn/2a/6a/2a6a50ff7abb6e38f06de4ec49979c0c_1888x717.png)
* org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
![](https://img.kancloud.cn/de/b5/deb59638f1bc651e21167fbcd08534b8_1548x650.png)
#### 总结
![](https://img.kancloud.cn/44/5a/445ab4fa1cb0485c5fb14e1ca7cbaf35_1408x686.png)
* pre:这种过滤器在请求被转发之前调用,一般用来实现身份验证等
* routing:这种路由是用来路由到不同的后端服务的,底层可以使用httpclient或者ribbon请求微服务
* post:当请求转发到微服务以后,会调用当前类型的过滤器。通常用来为响应天啊及标椎的HTTP Header、收集统计信息,等
* error:当发生错误是执行的过滤器
## 网关自定义过滤器
![](https://img.kancloud.cn/ff/8d/ff8dd2bf2c0115e60251acb458609831_425x429.png)
![](https://img.kancloud.cn/bc/db/bcdb2c3e56f1133b1fdeaab6b1042500_727x708.png)
* brave.servlet.TracingFilter :生成traceId
* com.open.capacity.common.filter.TraceContextFilter:传递traceId
* com.open.capacity.client.filter.AccessFilter:传递token
* com.open.capacity.client.filter.RequestStatFilter::传递traceId
* com.open.capacity.client.filter.ResponseStatFilter:响应头增加traceId
## 基于阿波罗配置中心的动态路由
参考代码:https://gitee.com/owenwangwen/config-center/tree/master/apollo-gateway
![](https://img.kancloud.cn/5a/2e/5a2e55a0d07d80b2b7cac37ac5948317_1538x727.png)
### 阿波罗官方已吸收此案例在github可下载
https://github.com/ctripcorp/apollo-use-cases/tree/master/spring-cloud-zuul/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul
* 创建项目
![](https://img.kancloud.cn/91/16/9116237874a9def0b31a593c72367189_853x421.png)
* 创建配置
![](https://img.kancloud.cn/f6/9e/f69ea6507e90366b9e79e86092e34faa_1217x523.png)
* 项目绑定配置
![](https://img.kancloud.cn/fc/b3/fcb3f24fb5551a6d7111d36a1a2bf135_1057x507.png)
## api-gateway 构建资源服务器
```
<!-- 资源服务器 -->
<dependency>
<groupId>com.open.capacity</groupId>
<artifactId>uaa-client-spring-boot-starter</artifactId>
</dependency>
```
[uaa-client-spring-boot-starter源码分析](https://www.kancloud.cn/owenwangwen/open-capacity-platform/1234951)
### 网关认证处理流程图
![](https://box.kancloud.cn/7eea410bb58d317640e825e7a3e7c20e_588x703.png)
### 网关白名单
```
security:
oauth2:
ignored: /test163/** , /api-auth/** , /doc.html ,/test111 ,/api-user/users-anon/login, /api-user/users/save, /user-center/users-anon/login,/document.html,**/v2/api-docs,/oauth/** ,/login.html ,/user/login,/**/**.css ,/**/**.js ,/getVersion
token:
store:
type: redis
```
### 网关统一异常
![](https://img.kancloud.cn/42/47/4247a93056cdf78440b1015f9384de2b_1401x484.png)
### 认证处理核心代码
```
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
String token = (String) authentication.getPrincipal();
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}
checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;
```
### 授权流程
![](https://box.kancloud.cn/53e8ffda6d12e4d48acdc6231bdafc4e_1408x773.png)
### 启用授权
![](https://box.kancloud.cn/feb228e1dd210009a0d4f57779cdb44b_1792x355.png)
```
@Bean
public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
```
## 网关认证授权总结
![](https://img.kancloud.cn/f3/4f/f34faab4ebaa42a7b18e895474f54332_1101x517.png)
* 访问者(Accessor)需要访问某个资源(Resource)是这个场景最原始的需求,但并不是谁都可以访问资源,也不是任何资源都允许任何人来访问,所以中间我们要加入一些检查和防护
* 在访问资源的所经之路上,可能遇到细菌,病毒,不管怎么样,对于要防护的资源来说最好的方法就是设关卡点,对于上图的FilterSecurityInvation,MethodIncation,Jointpoint,这些在spring security oauth中统称SecuredObjects
* 我们知道在哪里设置关卡点最合适,下一步就是设置关卡,对应FileSecurityInterceptor,MethodSecurityInterceptor,AspectSecurityInterceptor,
这些关卡统一的抽象类是AbstractSecurityInterceptor
* 有关卡点,关卡了以后,到底谁该拦截谁不应该呢,spring security oauth中由 AccessDecisionManager控制
* 最后一个问题,这个谁怎么定义,我们总得知道当前这个访问者是谁才能告诉AccessDecisionManager拦截还是放行,在spring security oauth框架中AuthenticationManager将解决访问者身份认证问题,只有确定你在册了,才可以给授权访问。AuthenticationManager,AccessDecisionManager,AbstractSecurityInterceptor属于spring security框架的基础铁三角。
* 有了以上骨架,真正执行防护任务的其实是SecurityFilterChain中定于的一系列Filter,其中ExceptionTranslationFilter,它负责接待或者送客,如果访问者来访,对方没有报上名来,那么,它就会让访客去登记认证(找AuthenticationManager做认证),如果对方报上名了,但认证失败,那么请重新认证送客,送客的方式是抛出相应的Exception,所以名字叫做ExceptionTranslationFilter。
* 最后,这个filter序列中可能不满足我们的需求,比如增加验证码,所以我们需要在其中穿插自己的Filter实现类,为定制和扩展Spring Security Oauth的防护体系。
* spring security内置的filter序列
![](https://img.kancloud.cn/5f/30/5f3006bb1d84ed61dbd12c71ec4c5099_1811x888.png)
* 执行过滤链
![](https://img.kancloud.cn/22/bc/22bcdefc6698fc9f5a5e7535566aa2dc_1270x348.png)
## 网关api权限设计
![](https://img.kancloud.cn/c1/68/c1685025219f816bb33f1840592b5599_855x351.png)
相同用户,不同应用的权限隔离
客户端模式 :
客户端A 申请的token ,可以访问/api-user/menu/current ,
客户端B 申请的token,不让访问/api-user/menu/current
密码模式:
客户端模式 :
客户端A admin用户 申请的token ,可以访问/api-user/menu/current ,
客户端B admin用户 申请的token,不让访问/api-user/menu/current
参考issue:[https://gitee.com/owenwangwen/open-capacity-platform/issues/IRG23]()
网关引依赖
![](https://img.kancloud.cn/11/b9/11b9dd2d759c8a097a81c0e5eaace90b_1594x285.png)
网关是否开启基于应用隔离,代码注释了,只是基于token的合法性校验,按建议开启是否启用api接口服务权限
OpenAuthorizeConfigManager
> 游乐场买了通票,有些地方可以随便玩,有些地方另外
> 单独校验买票
> config.anyRequest().authenticated() ;
> //这种通票,token校验正确访问
> config.anyRequest().access("@rbacService.hasPermission(request, authentication)"); //这种另外
> 单独校验,适用于网关对api权限校验
![](https://img.kancloud.cn/5c/2f/5c2f210132172402df18b9a824192230_1686x699.png)
通过clientID隔离服务权限
![](https://img.kancloud.cn/f6/16/f616866b31c57c5237dfe504060ae33b_1688x735.png)
(最新代码从新封装到网关,RbacService适用于api-gateway的api权限设计
![](https://img.kancloud.cn/64/c4/64c4b08dbde7005c43e6b5090e013188_1836x571.png))
通过应用分配服务权限
![](https://img.kancloud.cn/80/36/803611b8b8c3de222f87d60410699a32_1918x887.png)
## 网关hystrix-dashboard
![](https://img.kancloud.cn/4f/df/4fdfe9e8a33552a34e833306853c7fa0_1920x548.png)
## api-gateway 应用访问次数控制
![](https://img.kancloud.cn/11/80/118008b4b8789dbf860c9a5617a9bb07_851x616.png)
### oauth_client_details 增加
| 字段 | 备注 |
| --- | --- |
| if_limit| 是否需要控制访问次数 |
| limit_count| 访问阀值 |
![](https://box.kancloud.cn/59d3e6ffa172b92dc2b7d4fc7a7086e9_1908x332.png)
### auth-server 启动
#### redis存储结构
![](https://img.kancloud.cn/b1/63/b1631c777af4de495831b62a90fb6fc5_1915x947.png)
#### 加载oauth_client_details 到redis
![](https://img.kancloud.cn/4c/76/4c76aec57a406a4072e72a0c2bf44b6b_1504x535.png)
#### 应用访问次数控制过滤器
```
/**
* Created by owen on 2017/9/10. 根据应用 url 限流 oauth_client_details if_limit 限流开关
* limit_count 阈值
*/
@Component
public class RateLimitFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(RateLimitFilter.class);
private ThreadLocal<Result> error_info = new ThreadLocal<Result>();
@Autowired
private RedisLimiterUtils redisLimiterUtils;
@Autowired
private ObjectMapper objectMapper;
@Resource
SysClientServiceImpl sysClientServiceImpl;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (!checkLimit(request)) {
logger.error("too many requests!");
error_info.set(Result.failedWith(null, 429, "too many requests!"));
serverResponse(ctx, 429);
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/***
* 统一禁用输出
*
* @param ctx
* @param ret_message
* 输出消息
* @param http_code
* 返回码
*/
public void serverResponse(RequestContext ctx, int http_code) {
try {
ctx.setSendZuulResponse(false);
outputChineseByOutputStream(ctx.getResponse(), error_info);
ctx.setResponseStatusCode(http_code);
} catch (IOException e) {
StackTraceElement stackTraceElement= e.getStackTrace()[0];
logger.error("serverResponse:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());
}
}
/**
* 使用OutputStream流输出中文
*
* @param request
* @param response
* @throws IOException
*/
public void outputChineseByOutputStream(HttpServletResponse response, ThreadLocal<Result> data) throws IOException {
/**
* 使用OutputStream输出中文注意问题: 在服务器端,数据是以哪个码表输出的,那么就要控制客户端浏览器以相应的码表打开,
* 比如:outputStream.write("中国".getBytes("UTF-8"));//使用OutputStream流向客户端浏览器输出中文,以UTF-8的编码进行输出
* 此时就要控制客户端浏览器以UTF-8的编码打开,否则显示的时候就会出现中文乱码,那么在服务器端如何控制客户端浏览器以以UTF-8的编码显示数据呢?
* 可以通过设置响应头控制浏览器的行为,例如: response.setHeader("content-type",
* "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据
*/
OutputStream outputStream = response.getOutputStream();// 获取OutputStream输出流
response.setHeader("content-type", "application/json;charset=UTF-8");// 通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
/**
* data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,
* 如果是中文的操作系统环境,默认就是查找查GB2312的码表, 将字符转换成字节数组的过程就是将中文字符转换成GB2312的码表上对应的数字
* 比如: "中"在GB2312的码表上对应的数字是98 "国"在GB2312的码表上对应的数字是99
*/
/**
* getBytes()方法如果不带参数,那么就会根据操作系统的语言环境来选择转换码表,如果是中文操作系统,那么就使用GB2312的码表
*/
String msg = objectMapper.writeValueAsString(data.get());
byte[] dataByteArr = msg.getBytes("UTF-8");// 将字符转换成字节数组,指定以UTF-8编码进行转换
outputStream.write(dataByteArr);// 使用OutputStream流向客户端输出字节数组
}
public boolean checkLimit(HttpServletRequest request) {
// 解决zuul token传递问题
Authentication user = SecurityContextHolder.getContext().getAuthentication();
if (user != null) {
if (user instanceof OAuth2Authentication) {
try {
OAuth2Authentication athentication = (OAuth2Authentication) user;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) athentication.getDetails();
String clientId = athentication.getOAuth2Request().getClientId();
Map client = sysClientServiceImpl.getClient(clientId);
if(client!=null){
String flag = String.valueOf(client.get("if_limit") ) ;
if("1".equals(flag)){
String accessLimitCount = String.valueOf(client.get("limit_count") );
if (!accessLimitCount.isEmpty()) {
Result result = redisLimiterUtils.rateLimitOfDay(clientId, request.getRequestURI(),
Long.parseLong(accessLimitCount));
if (-1 == result.getResp_code()) {
logger.error("token:" + details.getTokenValue() + result.getResp_msg());
// ((ResultMsg)
// this.error_info.get()).setMsg("clientid:" +
// client_id + ":token:" + accessToken + ":" +
// result.getMsg());
// ((ResultMsg) this.error_info.get()).setCode(401);
return false;
}
}
}
}
} catch (Exception e) {
StackTraceElement stackTraceElement= e.getStackTrace()[0];
logger.error("checkLimit:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());
}
}
}
return true;
}
}
```
#### RedisLimiterUtils核心类
![](https://box.kancloud.cn/30574b5aa8bd6d72472b7f7aed00800f_1905x826.png)
```
@Component
public class RedisLimiterUtils {
public static final String API_WEB_TIME_KEY = "time_key:";
public static final String API_WEB_COUNTER_KEY = "counter_key:";
private static final String EXCEEDS_LIMIT = "规定的时间内超出了访问的限制!";
private static Logger logger = LoggerFactory.getLogger(RedisLimiterUtils.class);
@Resource
RedisTemplate<Object, Object> redisTemplate;
@Resource(name = "stringRedisTemplate")
ValueOperations<String, String> ops;
@Resource(name = "redisTemplate")
ValueOperations<Object, Object> objOps;
@Resource
RedisUtil redisUtil;
public Result IpRateLimiter(String ip, int limit, int timeout) {
String identifier = UUID.randomUUID().toString();
String time_key = "time_key:ip:" + ip;
String counter_key = "counter_key:ip:" + ip;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
redisUtil.set(time_key, identifier, timeout);
redisUtil.set(counter_key, 0);
}
if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {
logger.info(EXCEEDS_LIMIT);
return Result.failedWith(null, -1, EXCEEDS_LIMIT);
}
return Result.succeedWith(null, 0, "调用次数:" + this.ops.get(counter_key) );
}
public Result clientRateLimiter(String clientid, int limit, int timeout) {
String identifier = UUID.randomUUID().toString();
String time_key = "time_key:clientid:" + clientid;
String counter_key = "counter_key:clientid:" + clientid;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
redisUtil.set(time_key, identifier, timeout);
redisUtil.set(counter_key, 0);
}
if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {
logger.info(EXCEEDS_LIMIT);
return Result.failedWith(null, -1, EXCEEDS_LIMIT);
}
return Result.succeedWith(null, 0, "调用次数:" + this.ops.get(counter_key) );
}
public Result urlRateLimiter(String path, int limit, int timeout) {
String identifier = UUID.randomUUID().toString();
String time_key = "time_key:path:" + path;
String counter_key = "counter_key:path:" + path;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
redisUtil.set(time_key, identifier, timeout);
redisUtil.set(counter_key, 0);
}
if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {
logger.info(EXCEEDS_LIMIT);
return Result.failedWith(null, -1, EXCEEDS_LIMIT);
}
return Result.succeedWith(null, 0, "调用次数:" + this.ops.get(counter_key) );
}
public Result clientPathRateLimiter(String clientid, String access_path, int limit, int timeout) {
String identifier = UUID.randomUUID().toString();
LocalDate today = LocalDate.now();
String time_key = "time_key:clientid:" + clientid + ":path:" + access_path;
String counter_key = "counter_key:clientid:" + clientid + ":path:" + access_path;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
redisUtil.set(time_key, identifier, timeout);
redisUtil.set(counter_key, 0);
}
if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {
logger.info(EXCEEDS_LIMIT);
return Result.failedWith(null, -1, EXCEEDS_LIMIT);
}
return Result.succeedWith(null, 0, "调用次数:" + this.ops.get(counter_key) );
}
public Result rateLimitOfDay(String clientid, String access_path, long limit) {
String identifier = UUID.randomUUID().toString();
LocalDate today = LocalDate.now();
String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;
String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
//当天首次访问,初始化访问计数=0,有效期24h
redisUtil.set(time_key, identifier, 24 * 60 * 60);
redisUtil.set(counter_key, 0);
}
//累加访问次数, 超出配置的limit则返回错误
if (redisUtil.incr(counter_key, 1) > limit) {
logger.info("日内超出了访问的限制!");
return Result.failedWith(null, -1, "日内超出了访问的限制!");
}
return Result.succeedWith(null, 0, "调用总次数:" + this.ops.get(counter_key) );
}
public Result acquireRateLimiter(String clientid, String access_path, int limit, int timeout) {
String identifier = UUID.randomUUID().toString();
LocalDate today = LocalDate.now();
String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;
String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;
if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {
redisUtil.set(time_key, identifier, timeout);
redisUtil.set(counter_key, 0);
}
if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {
logger.info(EXCEEDS_LIMIT);
return Result.failedWith(null, -1, EXCEEDS_LIMIT);
}
return Result.succeedWith(null, 0, "调用次数:" + this.ops.get(counter_key) );
}
public void save(String tokenType, String Token, int timeout) {
redisUtil.set(tokenType, Token, timeout);
}
public String getToken(String tokenType) {
return redisUtil.get(tokenType).toString();
}
public void saveObject(String key, Object obj, long timeout) {
redisUtil.set(key, obj, timeout);
}
public void saveObject(String key, Object obj) {
redisUtil.set(key, obj);
}
public Object getObject(String key) {
return redisUtil.get(key);
}
public void removeObject(String key) {
redisUtil.del(key);
}
}
```
## 生产软负载NGINX构建ZUUL集群
![](https://img.kancloud.cn/9d/1e/9d1e85073fa1ba4ed17e5587cede9e1f_632x349.png)
## pom核心依赖
![](https://img.kancloud.cn/8d/a4/8da4ef28ed1db2f35dfcdc2bef724019_646x588.png)
- 前言
- 1.项目说明
- 2.项目更新日志
- 3.文档更新日志
- 01.快速开始
- 01.maven构建项目
- 02.环境安装
- 03.STS项目导入
- 03.IDEA项目导入
- 04.数据初始化
- 05.项目启动
- 06.付费文档说明
- 02.总体流程
- 1.oauth接口
- 2.架构设计图
- 3.微服务介绍
- 4.功能介绍
- 5.梳理流程
- 03.模块详解
- 01.老版本1.0.1分支模块讲解
- 01.db-core模块
- 02.api-commons模块
- 03.log-core模块
- 04.security-core模块
- 05.swagger-core模块
- 06.eureka-server模块
- 07.auth-server模块
- 08.auth-sso模块解析
- 09.user-center模块
- 10.api-gateway模块
- 11.file-center模块
- 12.log-center模块
- 13.batch-center模块
- 14.back-center模块
- 02.spring-boot-starter-web那点事
- 03.自定义db-spring-boot-starter
- 04.自定义log-spring-boot-starter
- 05.自定义redis-spring-boot-starter
- 06.自定义common-spring-boot-starter
- 07.自定义swagger-spring-boot-starter
- 08.自定义uaa-server-spring-boot-starter
- 09.自定义uaa-client-spring-boot-starter
- 10.自定义ribbon-spring-boot-starter
- 11.springboot启动原理
- 12.eureka-server模块
- 13.auth-server模块
- 14.user-center模块
- 15.api-gateway模块
- 16.file-center模块
- 17.log-center模块
- 18.back-center模块
- 19.auth-sso模块
- 20.admin-server模块
- 21.zipkin-center模块
- 22.job-center模块
- 23.batch-center
- 04.全新网关
- 01.基于spring cloud gateway的new-api-gateway
- 02.spring cloud gateway整合Spring Security Oauth
- 03.基于spring cloud gateway的redis动态路由
- 04.spring cloud gateway聚合swagger文档
- 05.技术详解
- 01.互联网系统设计原则
- 02.系统幂等性设计与实践
- 03.Oauth最简向导开发指南
- 04.oauth jdbc持久化策略
- 05.JWT token方式启用
- 06.token有效期的处理
- 07.@PreAuthorize注解分析
- 08.获取当前用户信息
- 09.认证授权白名单配置
- 10.OCP权限设计
- 11.服务安全流程
- 12.认证授权详解
- 13.验证码技术
- 14.短信验证码登录
- 15.动态数据源配置
- 16.分页插件使用
- 17.缓存击穿
- 18.分布式主键生成策略
- 19.分布式定时任务
- 20.分布式锁
- 21.网关多维度限流
- 22.跨域处理
- 23.容错限流
- 24.应用访问次数控制
- 25.统一业务异常处理
- 26.日志埋点
- 27.GPRC内部通信
- 28.服务间调用
- 29.ribbon负载均衡
- 30.微服务分布式跟踪
- 31.异步与线程传递变量
- 32.死信队列延时消息
- 33.单元测试用例
- 34.Greenwich.RELEASE升级
- 35.混沌工程质量保证
- 06.开发初探
- 1.开发技巧
- 2.crud例子
- 3.新建服务
- 4.区分前后台用户
- 07.分表分库
- 08.分布式事务
- 1.Seata介绍
- 2.Seata部署
- 09.shell部署
- 01.eureka-server
- 02.user-center
- 03.auth-server
- 04.api-gateway
- 05.file-center
- 06.log-center
- 07.back-center
- 08.编写shell脚本
- 09.集群shell部署
- 10.集群shell启动
- 11.部署阿里云问题
- 10.网关安全
- 1.openresty https保障服务安全
- 2.openresty WAF应用防火墙
- 3.openresty 高可用
- 11.docker配置
- 01.docker安装
- 02.Docker 开启远程API
- 03.采用docker方式打包到服务器
- 04.docker创建mysql
- 05.docker网络原理
- 06.docker实战
- 6.01.安装docker
- 6.02.管理镜像基本命令
- 6.03.容器管理
- 6.04容器数据持久化
- 6.05网络模式
- 6.06.Dockerfile
- 6.07.harbor部署
- 6.08.使用自定义镜像
- 12.统一监控中心
- 01.spring boot admin监控
- 02.Arthas诊断利器
- 03.nginx监控(filebeat+es+grafana)
- 04.Prometheus监控
- 05.redis监控(redis+prometheus+grafana)
- 06.mysql监控(mysqld_exporter+prometheus+grafana)
- 07.elasticsearch监控(elasticsearch-exporter+prometheus+grafana)
- 08.linux监控(node_exporter+prometheus+grafana)
- 09.micoservice监控
- 10.nacos监控
- 11.druid数据源监控
- 12.prometheus.yml
- 13.grafana告警
- 14.Alertmanager告警
- 15.监控微信告警
- 16.关于接口监控告警
- 17.prometheus-HA架构
- 18.总结
- 13.统一日志中心
- 01.统一日志中心建设意义
- 02.通过ELK收集mysql慢查询日志
- 03.通过elk收集微服务模块日志
- 04.通过elk收集nginx日志
- 05.统一日志中心性能优化
- 06.kibana安装部署
- 07.日志清理方案
- 08.日志性能测试指标
- 09.总结
- 14.数据查询平台
- 01.数据查询平台架构
- 02.mysql配置bin-log
- 03.单节点canal-server
- 04.canal-ha部署
- 05.canal-kafka部署
- 06.实时增量数据同步mysql
- 07.canal监控
- 08.clickhouse运维常见脚本
- 15.APM监控
- 1.Elastic APM
- 2.Skywalking
- 01.docker部署es
- 02.部署skywalking-server
- 03.部署skywalking-agent
- 16.压力测试
- 1.ocp.jmx
- 2.test.bat
- 3.压测脚本
- 4.压力报告
- 5.报告分析
- 6.压测平台
- 7.并发测试
- 8.wrk工具
- 9.nmon
- 10.jmh测试
- 17.SQL优化
- 1.oracle篇
- 01.基线测试
- 02.调优前奏
- 03.线上瓶颈定位
- 04.执行计划解读
- 05.高级SQL语句
- 06.SQL tuning
- 07.数据恢复
- 08.深入10053事件
- 09.深入10046事件
- 2.mysql篇
- 01.innodb存储引擎
- 02.BTree索引
- 03.执行计划
- 04.查询优化案例分析
- 05.为什么会走错索引
- 06.表连接优化问题
- 07.Connection连接参数
- 08.Centos7系统参数调优
- 09.mysql监控
- 10.高级SQL语句
- 11.常用维护脚本
- 12.percona-toolkit
- 18.redis高可用方案
- 1.免密登录
- 2.安装部署
- 3.配置文件
- 4.启动脚本
- 19.消息中间件搭建
- 19-01.rabbitmq集群搭建
- 01.rabbitmq01
- 02.rabbitmq02
- 03.rabbitmq03
- 04.镜像队列
- 05.haproxy搭建
- 06.keepalived
- 19-02.rocketmq搭建
- 19-03.kafka集群
- 20.mysql高可用方案
- 1.环境
- 2.mysql部署
- 3.Xtrabackup部署
- 4.Galera部署
- 5.galera for mysql 集群
- 6.haproxy+keepalived部署
- 21.es集群部署
- 22.生产实施优化
- 1.linux优化
- 2.jvm优化
- 3.feign优化
- 4.zuul性能优化
- 23.线上问题诊断
- 01.CPU性能评估工具
- 02.内存性能评估工具
- 03.IO性能评估工具
- 04.网络问题工具
- 05.综合诊断评估工具
- 06.案例诊断01
- 07.案例诊断02
- 08.案例诊断03
- 09.案例诊断04
- 10.远程debug
- 24.fiddler抓包实战
- 01.fiddler介绍
- 02.web端抓包
- 03.app抓包
- 25.疑难解答交流
- 01.有了auth/token获取token了为啥还要配置security的登录配置
- 02.权限数据存放在redis吗,代码在哪里啊
- 03.其他微服务和认证中心的关系
- 04.改包问题
- 05.use RequestContextListener or RequestContextFilter to expose the current request
- 06./oauth/token对应代码在哪里
- 07.验证码出不来
- 08./user/login
- 09.oauth无法自定义权限表达式
- 10.sleuth引发线程数过高问题
- 11.elk中使用7x版本问题
- 12.RedisCommandTimeoutException问题
- 13./oauth/token CPU过高
- 14.feign与权限标识符问题
- 15.动态路由RedisCommandInterruptedException: Command interrupted
- 26.学习资料
- 海量学习资料等你来拿
- 27.持续集成
- 01.git安装
- 02.代码仓库gitlab
- 03.代码仓库gogs
- 04.jdk&&maven
- 05.nexus安装
- 06.sonarqube
- 07.jenkins
- 28.Rancher部署
- 1.rancher-agent部署
- 2.rancher-server部署
- 3.ocp后端部署
- 4.演示前端部署
- 5.elk部署
- 6.docker私服搭建
- 7.rancher-server私服
- 8.rancher-agent docker私服
- 29.K8S部署OCP
- 01.准备OCP的构建环境和部署环境
- 02.部署顺序
- 03.在K8S上部署eureka-server
- 04.在K8S上部署mysql
- 05.在K8S上部署redis
- 06.在K8S上部署auth-server
- 07.在K8S上部署user-center
- 08.在K8S上部署api-gateway
- 09.在K8S上部署back-center
- 30.Spring Cloud Alibaba
- 01.统一的依赖管理
- 02.nacos-server
- 03.生产可用的Nacos集群
- 04.nacos配置中心
- 05.common.yaml
- 06.user-center
- 07.auth-server
- 08.api-gateway
- 09.log-center
- 10.file-center
- 11.back-center
- 12.sentinel-dashboard
- 12.01.sentinel流控规则
- 12.02.sentinel熔断降级规则
- 12.03.sentinel热点规则
- 12.04.sentinel系统规则
- 12.05.sentinel规则持久化
- 12.06.sentinel总结
- 13.sentinel整合openfeign
- 14.sentinel整合网关
- 1.sentinel整合zuul
- 2.sentinel整合scg
- 15.Dubbo与Nacos共存
- 31.Java源码剖析
- 01.基础数据类型和String
- 02.Arrays工具类
- 03.ArrayList源码分析
- 32.面试专题汇总
- 01.JVM专题汇总
- 02.多线程专题汇总
- 03.Spring专题汇总
- 04.springboot专题汇总
- 05.springcloud面试汇总
- 文档问题跟踪处理