MQ MQTT创建订单失败原因分析
出现的问题
12.12晚上灰度期间测试发现订购时创建订单会存在偶发的失败,由于现网的程序代码打印日志不够分析,这个问题作为遗留问题了。通过增加日志发现报错如下:从日志可以看出时由于创建订单解析op侧返回的fastjson解析出错,又增加日志打印创建订单返回的数据:
成功时返回数据:
responseEntity.getBody():{"state":"OK","body":true,"requestId":"reqId-65104a1da02e49a2998a59ddfb6eedb6"}
失败时返回数据:
responseEntity.getBody():{"state":"OK","body":true,"requestId":"reqId-0fd01d178adb4f218b07bf21b905598e"}
创建订单成功失败op侧返回数据都是一样的,但是解析为什么失败?
查找原因
public class ResponseBody {
private String state;
private String errorCode;
private String errorMessage;
private String requestId;
private T body;
}
public class ResourceV2OperAuthority {
public String extId;
public String orderId;
public String instanceId;
public String message;
public String code;
/**
* 续订:ALLOW:表示支持;FOREVER_DENY:表示永久不支持;TEMP_DENY:当前暂时不支持
*/
public String operate4Action;
}
创建订单时:
public ResponseBody createOrderToOP(String userId, String instanceId, String serviceInfoId, String orderExtId) {
CrudProductBody productBody = new CrudProductBody();
productBody.setUserId(userId);
productBody.setOrderType(Order.ORDERTYPE_NEW);
productBody.setOrderSource(Order.ORDER_SOURCE);
CrudProductExtBody extBody = new CrudProductExtBody();
extBody.setOrderExtId(orderExtId);
extBody.setInstanceId(instanceId);
extBody.setRegionId(regionId);
extBody.setServiceInfoId(serviceInfoId);
extBody.setPayType(MqConstant.PAY_TYPE_AFTER);
extBody.setChargingMode(MqConstant.USED_PAY);
List exts = new ArrayList<>();
exts.add(extBody);
productBody.setExts(exts);
log.info("productBody:{}", productBody.toString());
URI uri = signatureUrl(createUrl, HttpMethod.POST.toString(), null);
log.info("签名后的uri:{}", uri.toString());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("userId", userId);
ResponseEntity responseEntity = restTemplate.exchange(uri, HttpMethod.POST, new HttpEntity<>(productBody, headers), String.class);
ResponseBody response = JSON.parseObject(responseEntity.getBody(), ResponseBody.class);
return response;
}
退订时检验订单权限:
public ResponseBody checkOrderAuthority(String userId, String instanceId) {
Map paramMap = new HashMap<>(3);
paramMap.put("userId", userId);
paramMap.put("instanceId", instanceId);
paramMap.put("actionType", String.valueOf(MqConstant.Order.ORDERTYPE_CANCEL));
URI uri = signatureUrl(checkAuthorityUrl, HttpMethod.GET.toString(), paramMap);
String url = uri.toString() + "&userId=" + userId + "&instanceId=" + instanceId + "&actionType=" + MqConstant.Order.ORDERTYPE_CANCEL;
log.info("签名后的url:{}", url);
HttpHeaders headers = new HttpHeaders();
HttpEntity entity = new HttpEntity<>(headers);
ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
ResponseBody response = JSON.parseObject(responseEntity.getBody(), new TypeReference>() {
});
return response;
}
从代码能看到解析时采用了泛型和非泛型混用,结合现网出现这个问题时都是在退订操作以后出现的。为了复现问题,写了个测试demo:
public class Test {
public static void main(String[] args) {
String testJsonStr1 = "{\"state\":\"OK\",\"requestId\":\"reqId-0434e23da6d7476abe78a58356c47fd2\",\"body\":true}";
ResponseBody response1 = JSON.parseObject(testJsonStr1, ResponseBody.class);
System.out.println("response1 :" + response1);
String testJsonStr2 = "{\"state\":\"OK\", \"body\":{\"extId\":\"a7aa2db8c03647bbb61a6af65037452a\", \"orderId\":null, \"instanceId\":null, \"message\":null, \"code\": \"USERPORTAL_ORDER_BIZ_RESOURCE_NO_ACTION\", \"operate4Action\":\"FOREVER_DENY\" },\"requestId\":\"523b7187786c478783c053a61b84da5c\"}";
ResponseBody response2 = JSON.parseObject(testJsonStr2, new TypeReference>() {
});
System.out.println("response2 :" + response2);
String testJsonStr0 = "{\"state\":\"OK\",\"requestId\":\"reqId-0434e23da6d7476abe78a58356c47fd2\",\"body\":true}";
ResponseBody response0 = JSON.parseObject(testJsonStr0, ResponseBody.class);
System.out.println("response0 :" + response0);
}
}
其中 testJsonStr1、testJsonStr2、testJsonStr0是创建订单、退订时检查订单状态、创建订单时op侧返回的数据,执行程序结果如下:
response1 :ResponseBody{state='OK', errorCode='null', errorMessage='null', requestId='reqId-0434e23da6d7476abe78a58356c47fd2', body='true'}
response2 :ResponseBody{state='OK', errorCode='null', errorMessage='null', requestId='523b7187786c478783c053a61b84da5c', body='ResourceV2OperAuthority(extId=a7aa2db8c03647bbb61a6af65037452a, orderId=null, instanceId=null, message=null, code=USERPORTAL_ORDER_BIZ_RESOURCE_NO_ACTION, operate4Action=FOREVER_DENY)'}
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual true, pos 74, fieldName body
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:343)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:948)
at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_ResourceV2OperAuthority.deserialze(Unknown Source)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:62)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:790)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:595)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:188)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:642)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:350)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:254)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:467)
at com.example.demo.JsonTest.Test.main(Test.java:27)
报错信息跟现网一致,进而分析fastjson的源码:
public T parseObject(Type type, Object fieldName) {
int token = lexer.token();
if (token == JSONToken.NULL) {
lexer.nextToken();
return null;
}
if (token == JSONToken.LITERAL_STRING) {
if (type == byte[].class) {
byte[] bytes = lexer.bytesValue();
lexer.nextToken();
return (T) bytes;
}
if (type == char[].class) {
String strVal = lexer.stringVal();
lexer.nextToken();
return (T) strVal.toCharArray();
}
}
ObjectDeserializer derializer = config.getDeserializer(type);
try {
return (T) derializer.deserialze(this, type, fieldName);
} catch (JSONException e) {
throw e;
} catch (Throwable e) {
throw new JSONException(e.getMessage(), e);
}
}
出现解析问题的就是
ObjectDeserializer derializer = config.getDeserializer(type);
这一行代码,这行代码涉及了ParserConfig类,ParserConfig在反序列化的过程维护了常用类型和反序列化器的对应关系,并将该对应关系存放至IdentityHashMap。可以通过getDeserializer方法获得对象反序列化器ObjectDeserializer。以下时存放类型和反序列化器的对应关系的部分代码:
if (type instanceof Class>) {
return getDeserializer((Class>) type, type);
}
if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class>) {
return getDeserializer((Class>) rawType, type);
} else {
return getDeserializer(rawType);
}
}
当出现未定义的类型时,则会在获取该类型的反序列化器的同时建立起对应关系,并存放至IdentityHashMap
public void putDeserializer(Type type, ObjectDeserializer deserializer) {
deserializers.put(type, deserializer);
}
public boolean put(K key, V value) {
final int hash = System.identityHashCode(key);
final int bucket = hash & indexMask;
for (Entry entry = buckets[bucket]; entry != null; entry = entry.next) {
if (key == entry.key) {
entry.value = value;
return true;
}
}
Entry entry = new Entry(key, value, hash, buckets[bucket]);
buckets[bucket] = entry; // 并发是处理时会可能导致缓存丢失,但不影响正确性
return false;
}
测试demo中的testJsonStr1解析时,会把com.example.demo.JsonTest.ResponseBody这个类型和对应的反序列化器放到IdentityHashMap中:
最后就会运行程序报错:
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual true, pos 74, fieldName body
解决办法
升级fastjson可以解决这个问题,升级后的fastjson在解析testJsonStr0时获com.example.demo.JsonTest.ResponseBody这个类型对应的反序列化器,这个body结构并没有变成ResourceV2OperAuthority: 昨天前天晚上对比前后两个版本没有发现代码哪里会产生这个变化(待解决问题),我觉得在以后几个类代码修改了,后面研究一下:
com.alibaba.fastjson.parser.deserializer.FieldDeserializer
类FieldDeserializer用于存储字段所对应的反序列化器,其中Key对应为字段名称。
com.alibaba.fastjson.parser.JavaBeanDeserializer
类JavaBeanDeserializer是一个反序列化器。