微服务系列(三)--通过spring cloud zuul过滤器实现线上流量复制
思路
补充一下,为什么这里我会想到使用"pre"类型的过滤器实现流量复制/流量镜像。
刚开始的时候,参考了阿里的流量镜像实现方案: 配置流量复制策略,阿里的方案本身是对基于云原生envoy做的,这确实是istio原生能力。istio原生是通过配置spec.-mirror
这个参数,开启流量复制功能,阿里将这个功能白屏化并且对接了自己的监控,不得不承认,阿里对原生istio的很友好。
kubectl describe vs my-virtualservice
随后我尝试了sidecar注入、修改envoy配置,但皆以失败告终,一是平台不支持VirtualService,二是平台对Envoy做了一定的优化,配置文件里的各种参数魔改的让我摸不着头脑。
直到上周,突然想到流量复制使用envoy来做的原因之一是因为envoy充当了网关,那可不可以用zuul来实现?有了这个想法后,立即搜索了一遍网上对于zuul的特性描述,只有极少数的博客提到了zuul的复制功能,但均无现成的实现。问题不大,有可行性就行。
实现过程很容易联想到zuul的过滤器,因为pre过滤器可以完整地访问和修改请求信息,可以直接拿到请求并将其复制给镜像服务。有了这个思路一切就顺利多啦~
Spring Cloud代码
在zuul端创建class TrafficCopyFilter:
●假设provider-mirror是provider-demo的灰度应用
package com.tencent.tsf.msgw.zuul1.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import org.slf4j.Logger;
public class TrafficCopyFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(TrafficCopyFilter.class);
private RestTemplate restTemplate = new RestTemplate();
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 检查请求是否是发送给服务A的
if (request.getRequestURI().contains("/provider-demo")) {
LOG.info("复制请求到mirror服务");
// 将请求复制到服务B
String param = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/") + 1); // 提取参数
String bServiceUrl = "http://svcb host ip:port/echo/" + param; //这里写mirror服务的镜像地址,后期可从配置文件中读入
HttpMethod method = HttpMethod.valueOf(request.getMethod());
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.add(headerName, request.getHeader(headerName));
}
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
restTemplate.exchange(bServiceUrl, method, entity, Void.class);
} catch (Exception e) {
// 捕获异常,避免影响客户端
LOG.error("复制请求到mirror服务失败: {}", e.getMessage());
}
}
return null;
}
}
配置类
package com.tencent.tsf.msgw.zuul1.filter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZuulConfig {
@Bean
public TrafficCopyFilter trafficCopyFilter() {
return new TrafficCopyFilter();
}
}
最后,查看服务日志会发现,通过zuul请求provider /echo 接口时,流量会被复制到provider-mirror
遗留的问题
查看apm调用链的时候,会发现调用链缺失,分析原因是因为traceid是在流量入口的时候生成的,使用zuul复制流量的过程相当于做了内部的流量转发,不会在日志中生成traceid,也就无法生成调用链。