前段时间同事问我,我们录制的流量中,尤其是dubbo的子调用显示经常他的末尾会带上一个小尾巴这个是什么意思呢,其实之前我没有太在意这个事情,只是同事这么疑问了,确实激起了好奇心,所以就差了下 到底是什么
我们先看下是什么样的现象, 如下图所示:
如上, 这里就会跟上一个~AP
的小尾巴,那这个到底是什么意思呢,是某个hash的结果吗?要了解这个,其实也不难,我们就看下dubbo子调用的identity是怎么赋值的就好了。所以我们来看下具体的逻辑
在录制过程中,事件过来的时候,如果是Before的情况下,就会进入如下内容,
protected void doBefore(BeforeEvent event) throws ProcessControlException {
// 回放流量;如果是入口则放弃;子调用则进行mock
if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {
processor.doMock(event, entrance, invokeType);
return;
}
Invocation invocation = initInvocation(event);
invocation.setStart(System.currentTimeMillis());
invocation.setTraceId(Tracer.getTraceId());
invocation.setIndex(entrance ? 0 : SequenceGenerator.generate(Tracer.getTraceId()));
invocation.setIdentity(processor.assembleIdentity(event));
invocation.setEntrance(entrance);
invocation.setType(invokeType);
invocation.setProcessId(event.processId);
invocation.setInvokeId(event.invokeId);
invocation.setRequest(processor.assembleRequest(event));
invocation.setResponse(processor.assembleResponse(event));
invocation.setSerializeToken(ClassloaderBridge.instance().encode(event.javaClassLoader));
try {
// fix issue#14 : useGeneratedKeys
if (processor.inTimeSerializeRequest(invocation, event)) {
SerializerWrapper.inTimeSerialize(invocation);
}
} catch (SerializeException e) {
Tracer.getContext().setSampled(false);
log.error("Error occurred serialize", e);
}
RecordCache.cacheInvocation(event.invokeId, invocation);
}
所以identity
是经过各自的子调用处理器处理后生成的标识。这里我们具体的就是dubbo的调用,所以我们看下 DubboConsumerInvocationProcessor
的逻辑
public Identity assembleIdentity(BeforeEvent event) {
Object invoker;
Object invocation;
if (ON_RESPONSE.equals(event.javaMethodName)) {
// for record identity assemble
// onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {}
invoker = event.argumentArray[1];
invocation = event.argumentArray[2];
} else {
// for repeater identity assemble
// invoke(Invoker<?> invoker, Invocation invocation)
invoker = event.argumentArray[0];
invocation = event.argumentArray[1];
}
try {
// methodName
String methodName = (String) MethodUtils.invokeMethod(invocation, "getMethodName");
Class<?>[] parameterTypes = (Class<?>[]) MethodUtils.invokeMethod(invocation, "getParameterTypes");
// interfaceName
String interfaceName = ((Class)MethodUtils.invokeMethod(invoker, "getInterface")).getCanonicalName();
return new Identity(InvokeType.DUBBO.name(), interfaceName, getMethodDesc(methodName, parameterTypes), getExtra());
} catch (Exception e) {
// ignore
LogUtil.error("error occurred when assemble dubbo request", e);
}
return new Identity(InvokeType.DUBBO.name(), "unknown", "unknown", null);
}
我们看到Identity
直接就是通过 new Identity(InvokeType.DUBBO.name(), interfaceName, getMethodDesc(methodName, parameterTypes), getExtra())
完成的。 所以这的参数按照前面截图的来看的话,
-
InvokeType.DUBBO.name()
即dubbo
, -
interfaceName
则是com.xx.xx.api.service.UserAgreementApiService
-
getMethodDesc(methodName, parameterTypes)
这个看了下实现原来就是我们一直在寻找的带小尾巴的原因了, 我们来看下。protected String getMethodDesc(String methodName, Class<?>[] parameterTypes) { StringBuilder builder = new StringBuilder(methodName); if (parameterTypes != null && parameterTypes.length > 0) { builder.append("~"); for (Class<?> parameterType : parameterTypes) { String className = parameterType.getSimpleName(); builder.append(className.subSequence(0, 1)); } } return builder.toString(); }
我们可以看到它这里就是就是在拼接方法跟参数类型,不过他没有拿参数类型的所有内容,而是参数类型的类名,比如说 Java.lang.String 结果就是
string
了, 然后取这里的第一个字符。 -
getExtra() 暂时位置,看着是http的query的字段, 不过看到所有的录制的子调用录制逻辑基本都是null, 我们这里暂时就先忽略了。
那我们就直接看下Identity 的构造函数是怎么样的吧。
public Identity(String scheme, String location, String endpoint, Map<String, String> extra) {
this.scheme = scheme;
this.location = location;
this.endpoint = endpoint;
this.extra = extra;
StringBuilder sb = new StringBuilder();
sb.append(scheme).append(HOST_SPLITTER).append(Joiner.on("/").join(location, endpoint));
if (extra != null && !extra.isEmpty()) {
boolean firstKey = true;
for (Map.Entry<String, String> entry : extra.entrySet()) {
if (firstKey) {
firstKey = false;
sb.append(QUERY_STRING_COLLECTOR);
} else {
sb.append(KEY_VALUE_SPLITTER);
}
sb.append(entry.getKey()).append(KEY_VALUE_COLLECTOR).append(entry.getValue());
}
}
this.uri = sb.toString();
}
可以看到构造函数里面的逻辑其实重点是在构造一个uri, 而这个uri 应该就是我们最开始截图的时候看到的identity的内容了。 其实就是通过各个分隔符连接起来,构成我们传参进来的数据。