tips:本文完全来源于卢泽龙!!!
一、Gateway概述
1.1设计目标
1.2gateway基本功能
中文文档参考:https://cloud.tencent.com/developer/article/1403887?from=15425
三大核心:
二、引入依赖和yaml配置
1.1maven依赖(大坑:spring-web和actuaor一定不要引入!)
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
1.2BootStrap.yaml配置
spring:
application:
name: rical-gateway
cloud:
nacos:
discovery:
server-addr: xx.xxx.xx.xx:8848
config:
server-addr: xx.xxx.xx.xx:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
1.3动态路由配置rical-gateway-dev.yaml(nacos配置)
spring:
cloud:
gateway:
routes:
- id: search-movieInfo
uri: https://www.maoyan.com
predicates:
- Path=/films/**
- id: self-check
uri: http://localhost:8080
predicates:
- Path=/demo/selfCheck.json
rical:
gateway:
request:
qpslimit: 1
timeout: 0
三、自定义Filter
3.1、gateWay网关扩展点介绍
使用到gateway过滤器:
3.2、全局限流器
通过在nacos平台的rical-gateway-dev.yaml配置qpslimit和timeout这两个参数,实现限制qps和限流后的等待时间。遭到限制时,直接给客户端返回error响应!
@Component
@Slf4j
public class LimitFilter implements GlobalFilter, Ordered
{
@Value("${rical.gateway.request.qpslimit}")
private Double qpslimit;
@Value("${rical.gateway.request.timeout}")
private Integer timeout;
//创建一个限流器,参数代表每秒生成的令牌数(用户限流频率设置 每秒中限制qpslimit个请求)
RateLimiter rateLimiter;
/**
* 为什么要在bean的初始化方法赋值?
* 因为@value注解是在spring的 populateBean 方法中 通过 AutowiredAnnotationBeanPostProcessor后置处理器中赋值
* * 而如果直接在上面赋值他的执行时机是jvm启动时赋值,该步骤会在spring之前就会产生npe异常
*/
@PostConstruct
public void init(){
rateLimiter = RateLimiter.create(qpslimit);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
HttpHeaders header = response.getHeaders();
header.add("Content-Type", "application/json; charset=UTF-8");
RequestPath path = request.getPath();
//设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回
boolean tryAcquire = rateLimiter.tryAcquire(timeout, TimeUnit.SECONDS);
log.info("com.wtrue.rical.gateway.filter.LimitFilter.filter , tryAcquire = {}",tryAcquire);
if (!tryAcquire) {
JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Mono.just(buffer));
}
// 放行
return chain.filter(exchange);
}
private JSONObject setResultErrorMsg(String msg) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "406");
jsonObject.put("message", msg);
return jsonObject;
}
@Override
public int getOrder()
{
return 0;
}
}
3.3、全局日志打印器
注意:该日志打印器只打印配置好路由且没有被上面限流器限流的http请求(比如用户随便乱输入个路径进行请求是不会打印日志的)
@Component
@Slf4j
public class LoggerFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求信息
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress address = request.getRemoteAddress();
String method = request.getMethodValue();
URI uri = request.getURI();
HttpHeaders requestHeaders = request.getHeaders();
// 获取请求query
MultiValueMap<String, String> map = request.getQueryParams();
log.info("LoggerFilter.filter , request come in, please look down: \n{\n\trequest = {} \n\taddress = {} \n\tmethod = {} \n\turi = {} \n\trequestHeaders = {} \n\tmap = {}\n}",request,address,method,uri,requestHeaders,map);
// 2.获取响应信息
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
MultiValueMap<String, ResponseCookie> cookies = response.getCookies();
HttpHeaders responseHeaders = response.getHeaders();
log.info("LoggerFilter.filter , response is : \n{\n\tstatusCode = {} \n\tcookies = {} \n\tresponseHeaders = {}\n}",statusCode,cookies,responseHeaders);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
四、服务测试(类似美团OCTO功能)
1、大体流程图
2、具体实现方法
2.1gateway的export包提供一个所有业务方都能使用的controller,并通过springBoot自动装配的办法让业务方一引入依赖就能使用
@RestController
@Slf4j
public class SelfCheckController implements BeanFactoryAware {
/**
* 业务方引入该export包,就会把业务方自己的appname放进去
*/
@Value("${spring.application.name}")
private String appName;
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@PostMapping("selfCheck.json")
public String testInvoke(@RequestBody SelfCheckRequest request) {
try {
//1.安全检测
invokeSafeCheck(request);
//2.获取目标bean
Class<?> beanClazz = Class.forName(request.getBeanClazzName());
Object tartgetBean = getTartgetBean(beanClazz);
//3.转化参数类型列表
Class[] parameterTyps = transformParameterTypes(request.getParameterTypes());
//4.执行
Method method = beanClazz.getMethod(request.getMethodName(), parameterTyps);
Object returnValue = method.invoke(tartgetBean, request.getParameters());
return JSON.toJSONString(returnValue);
}catch (Exception e){
log.error("errMsg : {}",e.getMessage(),e);
return "自检异常:" + e.getMessage();
}
}
/**
* 转化参数类型列表
* @param parameterTypes
* @return
*/
@SneakyThrows
private Class[] transformParameterTypes(String[] parameterTypes) {
if (ArrayUtils.isEmpty(parameterTypes)) {
return new Class[0];
}
Class[] classes = new Class[parameterTypes.length];
int index = 0;
for (String parameterType : parameterTypes) {
Class<?> paramClzz = Class.forName(parameterType);
classes[index++] = paramClzz;
}
return classes;
}
/**
* 获取目标bean
* @param beanClazz
* @return
*/
private Object getTartgetBean(Class<?> beanClazz) {
Object bean = beanFactory.getBean(beanClazz);
if (bean == null){
throw new RuntimeException("本服务没有自检所需要的bean");
}
return bean;
}
/**
* 执行前的安全检测
* @param request
*/
private void invokeSafeCheck(SelfCheckRequest request) {
String targetAppName = request.getAppName();
if (!targetAppName.equals(appName)) {
throw new RuntimeException("自检错误!本服务不是目标服务,请检查appName是否传递错误~");
}
}
}
2.2 业务方一定要配置这两个参数,后面有大用
server.servlet.context-path=/demo
spring.application.name=demo
2.3 nacos配置路由规则
- id: self-check
uri: http://localhost:8080
predicates:
- Path=/demo/selfCheck.json
经过这样配置后,用户就能通过网关访问目标服务器的bean方法了,请求案例如下
POST localhost:10001/demo/selfCheck.json
{
"appName": "demo",
"beanClazzName": "com.example.demo.service.ITestService",
"methodName": "getTestMap",
"parameterTypes": [
"java.lang.String",
"java.lang.Integer"
],
"parameters": ["luzelong",999]
}
目标方法的代码如下:
public Map getTestMap(String key, Integer value) {
HashMap<String, Integer> map = new HashMap<>();
map.put(key,value);
log.info("demo!!!!!!");
return map;
}