开源地址
https://github.com/XiaoMi/mone/tree/master/gateway-all 欢迎对云原生技术/研发效能感兴趣的小伙伴加入(Fork、Star)我们。
一、网关里的filter链是做什么的
在gateway网关系列文章的第一篇里,我们知道网关的一个核心职能是收敛各个微服务公共非业务功能,如身份验证、监控、负载均衡、缓存等,以尽可能减少各服务的职责。在这里,我们用filter链来实现这个功能。
每个公共功能对应一个filter,比如身份验证filter、负载均衡filter等。网关组织这些filter生成一个有顺序的filter链路。第一步,request请求进入网关后,会顺序经过链路中的每个filter,这个时候我们可以对请求做一些处理。第二步,到达协议转换模块进行协议转换,分发到对应的后端应用并拿到返回结果。第三步,response结果逆序再次经过链路中的每个filter,这里我们可以对返回结果进行一定的处理。
二、设计一个filter链路
1、定义单个filter
RequestFilter: filter的抽象类,基于此抽象类实现具体的filter。里面定义了公共类成员变量和doFilter方法等。
FilterDef: 用一组数据(id、name)标记filter的唯一性,并额外记录filter的一些信息(version、author等)。
Invoker: 串联filter的关键。
FilterContext: 用于传递filter的上下文信息,比如ip,headers等
//省略一部分代码,可前往https://github.com/XiaoMi/mone/tree/master/gateway-all查看
@Slf4j
public abstract class RequestFilter {
//唯一定义某个filter
protected FilterDef def = new FilterDef(0, "", "", "");
public abstract FullHttpResponse doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request);
//上下线某个filter的开关
@Getter
@Setter
private volatile boolean offline = false;
//查看具体api是否开启某个filter
public boolean allow(ApiInfo apiInfo) {
if (null == apiInfo) {
log.info("filter error filter id:{} name:{}", this.def.getId(), this.def.getName());
return false;
}
if (null == apiInfo.getFilterInfoMap()) {
return false;
}
return apiInfo.getFilterInfoMap().containsKey(this.getDef().getName());
}
//停止filter
public void stop() {
String info = StringUtils.isEmpty(this.def.getName()) ? this.getClass().getSimpleName() : this.def.toString();
log.info("filter stop:{}", info);
}
//filter 初始化public void init() {
String info = StringUtils.isEmpty(this.def.getName()) ? this.getClass().getSimpleName() : this.def.toString();
log.info("filter init:{}", info);
}
}
public interface Invoker {
FullHttpResponse doInvoker(FilterContext context, ApiInfo apiInfo, FullHttpRequest request);
}
在定义filter抽象类时,里面有一个很重要的方法
doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request);
它和Invoker一起是串联起整个filter链路的关键。
2、加载filter链
RequestFilterChain: 主要有两个核心功能。一是加载filter并将它们组织成一条链路;二是具体请求进入filter链路的入口。
//省略一部分代码,可前往https://github.com/XiaoMi/mone/tree/master/gateway-all查看
@Component
public class RequestFilterChain implements IRequestFilterChain {
@Autowired
private ApplicationContext ac;
private volatile Invoker lastInvoker;
private final CopyOnWriteArrayList<RequestFilter> filterList = new CopyOnWriteArrayList<>();
//只初始化一次
private AtomicBoolean init = new AtomicBoolean(false);
@PostConstruct
public void init() {
reload("init", Lists.newArrayList());
}
//加载filterpublic void reload(String type, List<String> names) {
log.info("reload filter");
//获取所有系统定义的filter
Map<String, RequestFilter> map = ac.getBeansOfType(RequestFilter.class);
List<RequestFilter> list = new ArrayList<>(map.values());
log.info("system filter size:{}", list.size());
//对filter进行排序
list = sortFilterList(list);
//停止所有filter
stopFilter();
filterList.clear();
filterList.addAll(list);
init.set(false);
long begin = System.currentTimeMillis();
getLock();
try {
log.info("get write lock use time:{}", System.currentTimeMillis() - begin);
Invoker last = new GatewayInvoker();
int size = list.size();
//串联filter链路
for (int i = 0; i < size; i++) {
log.info("init filter index:{}", i);
RequestFilter filter = list.get(i);
filter.init();
Invoker next = last;
final int ti = i;
//Invoker的实现
last = (context, apiInfo, request) -> {
long now = System.currentTimeMillis();
FullHttpResponse res = filter.doFilter(context, next, apiInfo, request);
long rpcUseTime = filter.rpcFilter() ? 0 : context.getRpcUseTime();
long useTime = System.currentTimeMillis() - now - rpcUseTime;
if (useTime > 250) {
context.addTraceEvent("filter_" + ti + "_" + filter.getName(), useTime);
}
return res;
};
}
log.info("init filter finish");
this.lastInvoker = last;
} finally {
lock.writeLock().unlock();
log.info("reload end");
}
init.set(true);
}
//具体请求进入filter链路的入口
public FullHttpResponse doFilter(ApiInfo apiInfo, FullHttpRequest request, RequestContext context) {
if (!init.get()) {
return HttpResponseUtils.create(Result.fromException(new RuntimeException("filter init")));
}
try {
FilterContext ctx = this.initFilterContext(context);
try {
lock.readLock().lock();
return this.lastInvoker.doInvoker(ctx, apiInfo, request);
} finally {
lock.readLock().unlock();
}
} catch (Throwable ex) {
log.error(ex.getMessage(), ex);
throw ex;
}
}
}
3、filter链的执行顺序
FilterOrder: 使用注解来控制filter链路里filter的执行顺序。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Documented
public @interface FilterOrder {
/** * 越小的执行次序越靠前 * @return*/int value();
}
在RequestFilterChain里加载并初始化filter链路时,调用sortFilterList方法对有所有的filter进行排序
private List<RequestFilter> sortFilterList(List<RequestFilter> list) {
return list.stream().sorted((a, b) -> {
Integer x = a.getClass().getAnnotation(FilterOrder.class).value();
Integer y = b.getClass().getAnnotation(FilterOrder.class).value();
return y.compareTo(x);
}).collect(Collectors.toList());
}
三、以具体的filter举例
1、单个filter的执行
这里我们实现了一个简单的filter,他的主要作用是记录请求的入参和返回结果。请求进入网关到达LogFilter,我们首先判断该请求有没有开启日志记录,如果开启记录下请求日志,然后调用invoker.doInvoker(context, apiInfo, request)获得返回结果,并再次记录下返回结果的日志。
@Component
@FilterOrder(100)
public class LogFilter extends RequestFilter {
private static final Logger logger= LoggerFactory.getLogger(LogFilter.class);
@Override
public FullHttpResponse doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request) {
//判断具体请求有没有开启日志记录
if (apiInfo.isAllow(Flag.ALLOW_LOG)) {
long begin = System.currentTimeMillis();
String params = "";
if (request.method().equals(HttpMethod.GET)) {
params = HttpRequestUtils.getQueryString(request);
}
if (request.method().equals(HttpMethod.POST)) {
params = new String(HttpRequestUtils.getRequestBody(request));
}
String traceId = context.getTraceId();
logger.info("invoke begin : id:{} traceId:{} method:{} uri:{} params:{} headers:{}", apiInfo.getId(), traceId, request.method(), request.uri(), params, request.headers().toString());
try {
FullHttpResponse res = invoker.doInvoker(context, apiInfo, request);
String content = HttpResponseUtils.getContent(res);
logger.info("invoke end : id:{} traceId:{} method:{} uri:{} content:{} uid:{} useTime:{}", apiInfo.getId(), traceId, request.method(), request.uri(), content, context.getUid(), System.currentTimeMillis() - begin);
return res;
} catch (Throwable ex) {
//捕获下,然后打印异常
logger.warn("invoke error : id:{} traceId:{} method:{} uri:{} params:{} ex:{} useTime:{}", apiInfo.getId(), traceId, request.method(), request.uri(), params, ex.getMessage(), System.currentTimeMillis() - begin);
throw ex;
}
} else {
return invoker.doInvoker(context, apiInfo, request);
}
}
}
2、filter链路的执行
我们还以此图举例,order为1,2,3的filter组成一个filter链,请求进入网关后,request请求按按①、②、③的顺序被处理、获取到返回结果后按④、⑤、⑥的顺序经过filter,最终返回结果给调用方。