乐趣区

关于md5:一行MD5居然让小伙伴都回不了家

作者:京东批发 付伟

1. 前言

大家好,当你点开这篇文章的时候兴许心想是哪个 XX 小编混到这里,先不要焦急扔臭鸡蛋,本文是一篇规范(正经)的问题复盘文章。好了,一行 MD5 竟然让小伙伴下不了班,到底是什么问题呢,让咱们一起来看看吧。

2. 注释

2.1 需要是什么

这里不再介绍具体的业务。简而言之,有两个接口(查问、确认)对前端页面提供服务。

查问接口返回的数据依赖于本地数据与内部接口计算后的后果,也就是页面展现的是数据快照。确认接口是依照页面的展现后果申请内部接口。

思考到用户关上展现页面时的数据与提交操作可能距离很久,理论申请时后果已发生变化,而这种操作会影响业务后果。因而在提交时会进行一次 check,如果发现数据发生变化须要提醒页面进行刷新。

为了不便大家了解,我简略的画了个图,毕竟下面太啰嗦了。

  • 查问接口
  • 确认接口

尽管这个图有点粗率,然而置信看到这里的小伙伴(默认都是聪慧的)都对需要了然于胸了。

2.2 我怎么搞得

掰扯了半天,咱们的配角 MD5 还没有出场,别着急风雨总在彩虹后。

能够看出,这里须要前端将查问接口的返回值从新组装作为确认接口的入参。而后端须要再次走数据聚合的逻辑与前端传过来的业务值进行比拟,如果不匹配则提醒页面须要刷新。

所有看起来都牵强附会,那么小编遇到了什么问题呢?

简略来说有两点:

  1. 前端同学示意值不好传,因为这个页面比较复杂,具体起因小编也没深究,可能是被糊弄了。
  2. 后端同学(也就是小编)发现,这样查问接口和确认接口耦合很重大,如果确认接口须要新的入参,那么就须要改变查问接口。随着查问接口逻辑越来越简单,确认接口的一个入参就须要一层一层的传过来。很不敌对。

呵呵,机智的小编眉头一皱; 计上心来,便想到了了 MD5,看看百度百科怎么说

MD5 信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被宽泛应用的明码散列函数,能够产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输残缺统一。

一图胜千言

在工程,它差不多就是这么用。

String md5= Md5Utils.get(String source);

可能有聪慧的小伙伴会说了,这是散列函数存在哈希碰撞,不同的字符串也有可能生成雷同的哈希值。

是的没错,然而在小编的业务场景中,这种呈现的概率微不足道,忽略不计,解释权归小编所有。

那么具体怎么做的呢,还是看图谈话:

  • 革新后的查问接口
  • 革新后的确认接口

咱们须要对查问接口返回的业务集要害属性进行组合哈希,这样能够生成数据快照值。确认接口无需再传入业务汇合,只须要传入数据快照值,后端进行比照即可晓得是否产生变更。

一切都是那么的美妙,接下来就到了动人心魄的编码环节。话不多说,小编的我的项目中引入了 hutool 包,什么你不晓得糊涂包?

Hutool 是一个小而全的 Java 工具类库,通过静态方法封装,升高相干 API 的学习老本,进步工作效率,使 Java 领有函数式语言般的优雅,让 Java 语言也能够“甜甜的”。Hutool 中的工具办法来自每个用户的精雕细琢,它涵盖了 Java 开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型我的项目中的效率担当;

真不错,果然是效率担当,一行代码就搞定了。

    /**
     * 生成数据哈希
     */
    private String generateSnapShotHash(AcceptListQueryWrapResultDTO wrapResultDTO) {StringBuilder builder = new StringBuilder();
        for (AcceptListQueryResultDTO item : wrapResultDTO.getAllList()) {builder.append(item.getQuotationId()).append(item.getOperateType()).append(item.getPriceTypeCN());
        }
        return MD5.create().digestHex16(builder.toString());
    }

请各位看官记住这行代码

MD5.create().digestHex16(builder.toString());

毕竟它就是糊弄你点进来的罪魁祸首。

2.3 出了什么事

当小编开发完当前,开心的部署在了测试环境。和前端联调的时候,发现第一次申请总是超时???

一想可能是 mock 平台的问题,毕竟三方的查问接口还没开发实现,就不以为然。请留神,只是第一次超时。同样的申请参数第二次光速返回。呵呵,你说不是环境的问题,小编本人都不大信呢。

友方的接口开发完了,小编期待的换上了对方的接口。后果事实给了小编一记左勾拳,还是第一次超时。这不迷信?于是小编对本身产生了狐疑?难道不是环境的问题?

于是连忙在本地测试了一下,竟然是光速返回。作为自信的人肯定不是代码的问题,那么这个锅往哪里甩呢?又臭又硬的小编狠狠的思考了一分钟,又将锅甩给了业务网关(对立接管 HTTP 申请)必定是它的故障,毕竟测试环境的网关出问题很常见。

于是开开心心的筹备上预发了。上了预发相对没问题!!!小编山盟海誓的对 QA 说道。

上帝为你关上一扇门的同时也会为你关上一扇窗,预发环境第一次还是超时!!!小编感觉很羞愧对不起一起上线的小伙伴,毕竟大家都筹备十点下机了。

小编陷入了深思中。。。

2.4 怎么修好的

排查了预发环境的接口,友方的杰夫接口 TP99 只有几毫秒,网关也没有问题,兴许是数据库的起因,排查发现也没有问题。登时,小编又迷茫了。

山重水复疑无路柳暗花明又一村,机智的小编想到了国内出名厂商开源的一款 java 诊断工具 Arthas,利用它能够查看办法具体耗时。点我查看 被动关上另一扇窗。

当你遇到以下相似问题而大刀阔斧时,Arthas 能够帮忙你解决:

这个类从哪个 jar 包加载的?为什么会报各种类相干的 Exception?

我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?

遇到问题无奈在线上 debug,难道只能通过加日志再从新公布吗?

线上遇到某个用户的数据处理有问题,但线上同样无奈 debug,线下无奈重现!

是否有一个全局视角来查看零碎的运行状况?

有什么方法能够监控到 JVM 的实时运行状态?

怎么疾速定位利用的热点,生成火焰图?

怎么间接从 JVM 内查找某个类的实例?

因为预发环境还是比拟麻烦,于是小编在测试环境筹备好了 arthas 环境。

上面简略介绍下应用步骤:

  1. 下载全量包 arthas-bin.zip
  2. 解压
  3. chmod -777 arthas-boot.jar
  4. 启动 sudo -u admin -EH java -jar /home/export/App/arthas-boot.jar

当看到图标呈现时,即启动胜利。具体应用办法能够查看官网,此处不再赘述。

咱们应用 trace 命令查看办法耗时,同时在页面申请该查问接口。

trace  --skipJDKMethod false com.jd.universal.inquiry.service.protocol.jsf.AcceptListWebErpServiceImpl queryList

能够看到这行生成数据快照的办法,耗时占整个接口的 99.57%,紧接着咱们持续监控 generateSnapShotHash 办法:

trace  --skipJDKMethod false com.jd.universal.inquiry.service.protocol.jsf.AcceptListWebErpServiceImpl generateSnapShotHash

能够看到办法的耗时都集中在

[99.99% 36562.318173ms] cn.hutool.crypto.digest.MD5:create() #103

接着再次页面点击申请操作,呈现以下状况:

能够看到前面屡次申请
cn.hutool.crypto.digest.MD5:create() 办法耗时仅不到一毫秒。和咱们之前遇到的情况统一。此时已确定是这行 MD5 导致的第一次加载很慢。

尽管起因找到了,然而还是得看下为什么这行代码只有在第一次时这么慢,于是咱们进入该办法看看它到底搞什么幺蛾子。

能够看到初始化办法如下:

因为景象是程序第一次运行很慢,后续很快,依据小编多年的写 / 修 BUG 教训狐疑是这段初始化中存在动态加载。

MessageDigest 是 JDK 自带的类,为应用程序提供摘要算法的,这里咱们关注点就落在了下面的一行。咱们点进去看一下:

果然咱们看到了他在尝试加载 BouncyCastle 库,咱们来看一下这个库的介绍:

BouncyCastle(轻量级密码术包)是一种用于 Java 平台的开放源码的轻量级密码术包;Bouncycstle 蕴含了大量的明码算法,其反对椭圆曲线明码算法,并提供 JCE 1.2.1 的实现。

所以问题的答案就跃然纸上了,随着源码的深刻,咱们看到:

  private void setup()
   {loadAlgorithms(DIGEST_PACKAGE, DIGESTS);

        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_GENERIC);

        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_MACS);

        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_CIPHERS);

        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_GENERIC);

        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_CIPHERS);

        loadAlgorithms(KEYSTORE_PACKAGE, KEYSTORES);

        loadAlgorithms(SECURE_RANDOM_PACKAGE, SECURE_RANDOMS);

        loadPQCKeys();  // so we can handle certificates containing them.
     // 省略。。。}

正是因为这些算法实现的加载,导致 MD5.create() 第一次调用时耗时超过数十秒。

好了,既然找到了问题。那么改变起来就很简略了,小编尝试寻找了糊涂包中提供的办法,发现并没有入参能够敞开该三方加密包的初始化。于是换用了 Google 提供的 MD5 的实现。从新打包,部署,一次胜利,完满。

3. 后语

QA 同学在测试环境测出了这个问题,而自信的自己等闲视之,保持本人愚昧的观点,先认为是 Mock 的问题,接着又说是网关的问题。因为小编的自觉自信,导致上线到很晚,示意十分的羞愧。总结失败的起因:

  1. 正当评估应用第三方包
  2. 测试环境遇到的问题尽力去追,不要自觉下结论
  3. 要听 QA 的话

4. 参考

Bouncy Castle 加密算法包

arthas 官网文档

应用 Arthas 进行生产代码热修复

退出移动版