关注公众号【1024个为什么】,及时接收最新推送文章!
本文内容: 1、背景 2、技术方案 3、翻车了 |
|| 背景
前段时间搞数据加密,需要把数据库中现有的某些列加密存储。
公司定的技术方案是由服务层做这项工作,不同的服务分配不同的秘钥,秘钥泄露也只会影响这一个服务,安全性更高。架构组提供加密算法包,各业务自己完成加解密的工作。
这其中有一项艰巨的任务,对历史数据的加密。由于不能在数据库层面批量处理,架构组也没有提供统一处理的方案,各业务只能自行解决。
我认为像这种和业务无关的事情,应该出一个通用的解决方案。一来可以减少各业务重复开发的工作量,二来也可以避免各业务试错成本。
|| 技术方案
所以我在设计方案的时候,把上面说的都考虑了进去,做了一个通用的技术方案,支持试刷、重刷、校验功能。各业务整个流程都是一样的,不同的只是密钥、库表、字段信息,这些都放在了配置中心。
接下来就该由它完成这项艰巨的任务了。
我先用它把自己负责的几个服务的数据都刷完了,一切都很顺利,速度也能接受,20W/min,接着又刷了其他几个同事的数据,也很顺利。
|| 翻车了
上面刷完的单表数据量都不大,最大的也不到 300W 。
在刷另一个同事的数据时,执行了大概 20 分钟左右,速度明显慢了下来,而且越来越慢,只能停了重试,重试现象依旧。
猜着可能是分页的问题,排查发现条件里的 id 用的就是主键 id,不应该慢啊。
又找同事一起研究了一下代码逻辑,也没看出什么问题。
继续重试,半小时左右的时候,报了内存溢出,所以只能先拿到内存快照才能分析什么原因了。
由于执行任务的机器没有加 gc、dump 相关的配置,把任务切到有配置的机器上执行。执行过程中,通过 gc 日志监控 full gc 情况,发现不到 10 分钟就触发 full gc 了,越来越频繁,直到刷屏。等内存溢出后,拿到 dump 文件。
说明一下:我们为了兼顾性能和故障可追溯,线上机器,只选了一台配置了 loggc、HeapDumpPath,因为只要程序有问题,每台机器都会有问题,有一台留痕就可以了。权限也有严格的控制,像 jstat 这种命令都是禁用的。
这里引申出一个面试题,有面试官会问怎么拿到 dump 文件,因为大部分同学工作中很少遇到这种场景。
有人说可以用 jvisualvm 的远程登录分析功能,这个吗,自己玩玩还行,千万不能用在线上,估计你也没有权限。
我这次任务的服务 JVM 配的是 1G 内存,生成的 dump 文件有 1.1G,压缩后 402M,算是比较小的。但是也不能直接从服务器直接下载,不仅速度慢,还会影响服务的带宽。因为大多服务都是内网之间的调用,这类机器的外网带宽很小的,就算允许你下载,没几个小时也下载不完。
说一下我司的流程,会先把文件转存到 FTP 服务器上,因为是内网,耗时可以忽略,再通过 FTP 服务器下载,这样既不影响服务,下载速度也很快。其实运维同学已经把这个流程已经集成在运维系统了,我们直接选择要下载的文件,系统就会自动转存并生成一个链接,点击链接就能下载。
接下来就是分析 dump 文件了。我用的是 IBM 的 HeapAnalyzer,直接通过 jar 包启动,需要注意的是要把启动内存设置大一点,默认的太小了,至少是 dump 文件大小的1.5 倍,否则还没等文件打开,软件就挂了。
打开文件后,一眼就看到问题原因了
没错,就是它了,PreparedStatement,直接找到代码,发现每次循环处理结束后,PreparedStatement 没有关闭,只是在最后任务结束的时候把 Connection 关闭了。
这是一个很低级的错误,犯错的原因是:
1、工作中几乎没有过手动 JDBC 的使用,手生了
2、还是对这部分的底层原理不清楚,脑子里就知道 Connection 要关闭,没意识到 PreparedStatement 也必须要关闭。
原因找到了,问题也随之解决,但脑子里却冒出了更多的问号。
为什么 PreparedStatement 需要关闭?
为什么导致内存溢出的不是 ResultSet, 它也没关闭啊?
MySQL 对 PreparedStatement 的数量就没限制吗,可以一直创建?
……
我们下期接着讨论。
最后再引申出一个面试题:发生内存溢出后,服务还能正常访问吗?
扯两句
到什么时候也不能丢了基本功啊
过度关注业务逻辑可能会导致思路跑偏
原创不易,如有收获,一键三连,感谢支持!