zoukankan      html  css  js  c++  java
  • SpringMVC底层数据传输校验的方案

    团队的项目正常运行了很久,但近期偶尔会出现BUG。目前观察到的有两种场景:一是大批量提交业务请求,二是生成批量导出文件。出错后,再执行一次就又正常了。

    经过跟踪日志,发现是在Server之间进行json格式大数据量传输时会丢失部分字符,造成接收方拿到完整字符串后不能正确解析成json,因此报错。

    同其他团队同事们沟通后发现,不仅仅是我们项目有这个问题,我们不是一个人在战斗。

    1 问题现象

    服务器之间使用http+json的数据传输方案,在传输过程中,一些json数据发生错误,导致数据接收方解析json报错,系统功能因此失败。

    下面截取了一小段真实数据错误,在传输的json中,有一个数据项是departmentIdList,其内容时一个长整型数组。

     

    传输之前的数据为:

    "departmentIdList" : [ 719, 721, 722, 723, 7367, 7369, 7371, 7373, 7375, 7377 ]

    接收到的数据为:

    "departmentIdlist" : [ 719, 721'373, 7375, 7377 ]

    可以看到,这个错误导致了两个问题:

    1、 json解析失败

    2、 丢失了一些有效数据

    详细检查系统日志之后,这是偶发bug,并且只在传输数据较大时发生。

    2 可选的解决方案

    2.1 请架构组协助解决

    这是最直接的解决方案,因为我们项目使用架构组提供的环境,他们需要提供可靠的底层数据传输机制。

    2.2 压缩传输数据

    因为数据量大时容易发生,并且传输的都是普通文本,可以考虑对内容进行压缩后传输。普通文件压缩率也很高,压缩后内容长度能做到原数据10%以内,极大减少传输出错的几率。

    2.3 对传输数据进行MD5校验

    将传输数据作为一个完整数据块,传输之前先做一个md5摘要,并将原数据和摘要一并发送;接收方收到数据后,先进行数据校验工作,校验成功后再进行后续操作流程,如果不成功可以辅助重传或直接报错等机制。

    3 方案设计

    为了彻底解决这个问题,设计了一个底层方案

    3.1 设计原则

    1、 适用类型:Spring MVC项目,数据发送方使用RestTemplate工具类,使用fastjson作为json工具类。

    2、 数据校验,使用MD5加密,当然也可以配合数据压缩机制,减少传输数据量。

    3、 提供底层解决方案,不需要对系统代码做大规模调整。

    3.2 核心设计

     

    数据发送方,重载RestTemplate,在数据传输之前对数据进行md5摘要,并将原始数据和 md5摘要一并传输。

    数据接收方,重载AbstractHttpMessageConverter,接收到数据后,对数据进行MD5校验。

    3.3 DigestRestTemplate关键代码

    对原json进行摘要,并同原始数据一起生成一个新的json对象。

    private Object digestingJson(JSONObject json) throws Exception {

           String requestJsonMd5 = JsonDigestUtil.createMD5(json);

           JSONObject newJson = new JSONObject();

           newJson.put("content", json);

           newJson.put("md5", requestJsonMd5);

           return newJson;

    }

    重载的postForEntity函数核心部分,如果传入参数是 JSONObject,则调用方法对数据进行摘要操作,并用新生成的json进行传输。

    Object newRequest = null;

    if (request instanceof JSONObject) {

           JSONObject json = (JSONObject) request;

           try {

                  newRequest = digestingJson(json);

           } catch (Exception e) {

           }

    }

    if (newRequest == null) {

           newRequest = request;

    }

    return super.postForEntity(url, newRequest, responseType);

    3.4 DigestFastJsonHttpMessageConverter 核心代码

    首先会判断是否是经过md5摘要的json,是有摘要的数据进行校验,否则直接返回对象。

    private JSONObject getDigestedJson(JSONObject json) {

      if (json.size()==2&&json.containsKey("md5")&&json.containsKey("content")) {

        String md5 = json.getString("md5");

        String content = json.getString("content");

        logger.info("degested json : {}", json);

        try {

          String newMd5 = JsonDigestUtil.createMD5(content);

          if (newMd5.equals(md5)) {

            json = JSON.parseObject(content);

          } else {

            logger.error("md5 is not same : {} vs {}", md5, newMd5);

            throw new RuntimeException("content is modified");

          }

        } catch (Exception e) {

        }

      } else {

        logger.info("may not be digested json");

      }

      return json;

    }

    原有的处理数据代码增加调用该方法的代码

    @Override

    protected Object readInternal(Class<? extends Object> clazz,

    HttpInputMessage inputMessage)

        throws IOException, HttpMessageNotReadableException {

      JSONObject json = null;

      InputStream in = inputMessage.getBody();

      Charset jsonCharset = fastJsonConfig.getCharset();

      Feature[] jsonFeatures = fastJsonConfig.getFeatures();

      json = JSON.parseObject(in, jsonCharset, clazz, jsonFeatures);

      json = getDigestedJson(json);

      return json;

    }

    当前的代码,如果数据校验失败,简单抛出异常。后续可以增加更多的机制,比如在RestTemplate处增加校验,如果发现校验失败,则重传。

    3.5 数据发送方项目配置

    以Spring Boot项目为例

    在Main类中定义 restTemplate

    @Bean(name = "restTemplate")

    public RestTemplate getRestTemplate() {

      RestTemplate restTemplate = new DigestRestTemplate();

      return restTemplate;

    }

    需要调用RestTemplate的代码,只需要依赖注入RestTemplate

    @Autowired

    RestTemplate restTemplate;

    3.6 数据接收方项目设置

    在SpringBootApplication类中定义

    @Bean

    public HttpMessageConverters fastJsonHttpMessageConverters() {

      DigestFastJsonHttpMessageConverter fastConverter =

        new DigestFastJsonHttpMessageConverter();

      FastJsonConfig fastJsonConfig = new FastJsonConfig();

      fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);

      fastConverter.setFastJsonConfig(fastJsonConfig);

      HttpMessageConverter<?> converter = fastConverter;

      return new HttpMessageConverters(converter);

    }

    4 后记

    经过测试,这个方案是可行的。如果为了能够适应更多的项目及更多的Java技术栈,需要对代码进行进一步完善。

     

  • 相关阅读:
    转贴: AV音响连接方法 回上个问题,次世代音轨通过HDMI透传给了功放,那图像信号怎么给到电视? 这篇文章教你
    openmosix学习心得,openmosix和pbs系统的差异
    转载:高清音频格式详解
    PVFS2 Failover Policy - 当一块硬盘坏了以后,PVFS如何应对?数据会丢失么?
    转贴: 浅析多声道LPCM TRUE HD DTS HD之异同
    [存储知识]SAN和NAS的进一步解析
    转贴: 次时代音轨
    7×24小时硬盘-正在走向桌面用户的企业级硬盘
    Learning NFS/NIS 2nd 读书笔记-Chapter1 Network Fundamentals
    新闻管理页面源码
  • 原文地址:https://www.cnblogs.com/codestory/p/6757373.html
Copyright © 2011-2022 走看看