fastjson缓存问题

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中:
image.png

image.png

testJsonStr2解析时,会取得这个反序列列化器:
image.png
在反序列化以后会把反序列化器的body对应的字段改为:
image.png
testJsonStr0解析时,会取得com.example.demo.JsonTest.ResponseBody这个类型对应的反序列化器,但是testJsonStr0解析时body对应的的是字符串“true”,而通过这个反序列化器取得的结构是ResourceV2OperAuthority这个对象:
image.png

最后就会运行程序报错:

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:
image.png

  昨天前天晚上对比前后两个版本没有发现代码哪里会产生这个变化(待解决问题),我觉得在以后几个类代码修改了,后面研究一下:
com.alibaba.fastjson.parser.deserializer.FieldDeserializer
类FieldDeserializer用于存储字段所对应的反序列化器,其中Key对应为字段名称。
com.alibaba.fastjson.parser.JavaBeanDeserializer
类JavaBeanDeserializer是一个反序列化器。

你可能感兴趣的:(fastjson缓存问题)