图片的批量导入实现和对主键生成策略的思考
CDX一期上线的那天是我来公司最紧张的一天,因为这一天我要负责公司近5000张车型图的导入工作。虽然在完成上传和导入的代码之后测试了很多遍,但是真正到了正式环境,却真的很怕出问题。内搜、gift、以及本地和线上数据库的同步,哪一个环节出了问题都会影响上线的时间。现在一期上了线,也对自己图片上传导入这部分做一些总结,还有对现有方法不足之处的一些思考。
其实一张图的上传逻辑还是比较简单的:
1. 上传图片到gift服务器。
2. 保存图片信息到线上数据库。
3. 将图片信息按固定格式推送给内搜,便于之后检索。
现在公司有几千张图片,不可能人工的一张一张在页面上传,这就需要我来做一个批量上传导入的脚本,实现逻辑大概如下:
1. 从设计部门拷贝资源库素材到本地。
2. 遍历本地资源文件夹,取出所有的素材文件。
3. 把所有素材文件归类,将对应路径格式化为标签,做文件名的正则校验,筛选出满足要求的素材。
4. 把取到的所有素材文件依次上传到gift,压缩图传到public空间,原图传到private空间。
5. 将上传成功的素材信息,标签等写入本地数据库。
6. 将图片发布,信息推送到内搜系统的队列中。
7. 完成这个过程后,将本地数据库新增的数据和线上数据库进行同步。
看完这个过程,可能自己都会有疑问,为什么有一个本地和线上数据库同步的步骤?这是由于线上数据库的连接地址vip只能线上服务器访问,而本地并无权限,并且又不能把素材包和导入脚本统统放到线上环境去跑。这就导致了在这个过程中间会产生短暂的不一致现象:展现在web端就是,搜索发现有了这个图(因为内搜已经消费了推送数据),但是点开详情却发现无此图片。
但这个问题还是小问题,有个问题更无法忽视:数据库的主键是自增的,那如何保证数据库同步的过程中不会出现主键的冲突?这些都是我在下面想说的。
1. 批量上传代码的实现中的要点:
- 遍历文件夹使用了非递归的方式,因为要记录每一级的文件夹信息,作为该图的标签keyLabels;另外对于文件的格式,文件名的规则做了正则校验,这个和设计部门达成了统一。
- 由于浏览器端上传的图片到了服务端是作为MultipartFile类型去接收的,由于MultipartFile是个接口,我必须要有实现该接口的类去对接本地遍历之后得到的File集合,这里我仿照springfreamwork.mock.web包下的MockMultipartFile,写了一个相似的类,其中加了一些符合需求的字段扩展。
- 在测试的过程中出现了JVM的OOM问题,这是由于我把文件夹下所有的图片一起读取,然后写到了几千个MockMultipartFile中的buffer中,难怪会内存溢出。这里我做了分段上传的处理,对files文件列表进行了等间隔的划分,解决了OOM的问题。
2. 导出过程错误日志的配置:
在这些图片导入的过程中,如果某个图片在某一环节抛了异常,有可能会出现内搜,gift,数据库三方数据不一致的情况,这是我们绝对不允许的。所以一定要做错误日志的记录,导入完成去查看错误日志,定位到某个图片,从而解决对应的不一致数据。这里的日志配置如下:
<!-- 图片导入环境 --> <springProfile name="import"> <logger name="com.xiaojukeji" level="error" /> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${normal-pattern}</pattern> <charset>${encoding}</charset> </encoder> </appender> <root level="error"> <appender-ref ref="console"/> </root> </springProfile>
可以看到,我是新建了一个import的spring.profiles,在这个模式下,我只记录错误的日志信息,并且我这里试输出到了控制台,便于我及时发现问题。(事实上,正式导入过程很顺利,没有出现任何问题~)
3. 数据库同步的问题和思考
刚刚说到在所有图片都导入gift和内搜的正式环境之后,需要把数据库的新增信息同步到线上数据库。由于主键是自增的,实现的时候会有主键冲突的可能性。这里暂时处理的方法并不好:取了一段主键间隔,并按历史数据分析不会出现上传量大于间隔的情况,然后来避免这一冲突。(事实上,理论上还是存在冲突的可能性)
那关于这一点的改进,我也想过一些方法。
首先想到的是希望用全局唯一的主键uuid代替自增主键。但是innodb的自增主键确实性能高,大多时候还是需要采用的,反观uuid,由于长度很长,考虑因素颇多,导致其性能一直是人们诟病的地方。
然后看到部门的代码脚手架中有一个IDGeneratorUtils,是公司内的一个主键生成器,里面的思想类似于uuid,也会包含时间戳、机器码等信息,但是实现比较轻量级,性能也会更好。
写在最后:除了以上所说,这部分导入功能还包含了线程池,内存io等细节,很多地方都是可以作为优化点的。希望有时间有机会可以采用这种方案进行改进,并且通过代码的重构使得图片导入的这个过程更加智能和方便。