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<T> {
    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<CrudProductExtBody> 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<String> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, new HttpEntity<>(productBody, headers), String.class);
        ResponseBody response = JSON.parseObject(responseEntity.getBody(), ResponseBody.class);
        return response;
    }

退订时检验订单权限:

    public ResponseBody<ResourceV2OperAuthority> checkOrderAuthority(String userId, String instanceId) {
        Map<String, String> 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<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        ResponseBody<ResourceV2OperAuthority> response = JSON.parseObject(responseEntity.getBody(), new TypeReference<ResponseBody<ResourceV2OperAuthority>>() {
        });
        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<ResourceV2OperAuthority> response2 = JSON.parseObject(testJsonStr2, new TypeReference<ResponseBody<ResourceV2OperAuthority>>() {
        });
        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> 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<K, V> entry = buckets[bucket]; entry != null; entry = entry.next) {
            if (key == entry.key) {
                entry.value = value;
                return true;
            }
        }

        Entry<K, V> entry = new Entry<K, V>(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是一个反序列化器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容

  • FastJson是一个近几年非常热门的第三方java库,它以它强大的功能和出色的性能表现而广为人知。那么,究竟为何...
    月强阅读 13,435评论 0 15
  • 近期公司公网接口被频发攻击刷垃圾数据,某些不常用接口一晚上被刷了几十万次,此背景下接口项目频繁出现OOM的情况,主...
    InsideEvil阅读 2,414评论 1 1
  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,741评论 0 24
  • JSON,全称:JavaScript Object Notation,作为一个常见的轻量级的数据交换格式,应该在一...
    Java_Explorer阅读 3,317评论 2 10
  • 高二分科后我选择了文科,可事实上我的语文成绩很差。我时时懊恼,厌烦,也很讨厌应试的那一套,用现在的话说,就...
    知心头阅读 521评论 0 0