背景
市面上云存储产品蛮多的,没有云的公司,存储一直是个问题,需要花费昂贵的资源存储数据或一些业务需要产生的文件,对于附件,不管时间多长,用户需要的时候就要展示,但频率不高。
现状
公司当前使用的是阿里云服务,存储自然是OSS,这玩意儿无论是按使用量付费还是包年,总归是要出钱的,区别就是钱多钱少的问题,运维同学自然是想越少越好,每个月找老板签字不会被BB。
近期做一个业务,需要下载OSS上的(视频)附件,之后调用算法脚本,他们会解析视频内容按业务生成其它数据。关于Java如何调用算法可以参考:《批量更新SQL如何写?》一篇文末有讲(名字怪是因为当时做那个业务也需要调用算法就放在里面了),有兴趣的同学可以转一下身,跳过去看看。
随着业务产生的视频文件越来多,历史文件跑过算法后属于冷数据,原始大小存储太占用空间浪费钱了,而且还不断有新的文件继续进来,这个时候算法同学想到了要节约空间、节约上传时间(也就变向节约了带宽,这些都是钱呐),想到了对文件进行压缩的正常操作,有一说一,压缩后的文件是.lz4我是第一次接触,正常压缩大家都是zip、rar、tar,新后缀着实让我开了眼
学习
官网(https://lz4.org)对LZ4的概述如下:
译:LZ4 是一种无损压缩算法,每个内核的压缩速度大于 500 MB/s(大于 0.15 字节/周期)。它的解码器速度极快,每个内核可达到数 GB/秒(~1 字节/周期)。它还有一个名为 LZ4_HC 的高压缩衍生工具,可根据压缩率定制 CPU 时间。LZ4 库采用 BSD 许可作为开源软件提供。(by www.deepl.com)
压缩率:
lz4: 559 ÷ 2048 ≈ 0.27
zip: 445 ÷ 1638 ≈ 0.28
假设上传速度 10 MB/s ,下载速度100 MB/s,对于一个100TB的数据在不压缩的情况下:
上传时间为:(100 TB * (1000^2) MB/TB) ÷ (10MB/s*3600s/hour) ≈ 2777 hours ≈ 115 days
下载时间为:(100 TB * (1000^2) MB/TB) ÷ (100MB/s*3600s/hour) ≈ 277 hours ≈ 11 days
zip压缩后:100T*0.28 = 28T
上传时间:
(28 TB * (1000^2) MB/TB) ÷ (10 MB/s * 3600 s/hour) ≈ 777 hours ≈ 32 days
下载时间:
(28 TB * (1000^2) MB/TB) ÷ (100MB/s * 3600 s/hour) ≈ 77 hours ≈ 3 days
lz4压缩后:100T*0.27= 27T
上传时间:
(27 TB * (1000^2) MB/TB) ÷ (10 MB/s * 3600 s/hour) ≈ 750 hours ≈ 31days
下载时间:
(27 TB * (1000^2) MB/TB) ÷ (100MB/s * 3600 s/hour) ≈ 75 hours ≈ 3 days
上面是随便找了个压缩软件做的一个例子对比,本来想准备多弄几个压缩算法的对比数据,但一想到各位都比我聪明,能意会说明目的就达到了。
适配
原始文件的压缩后我这边需要改动的有两点:a.原来下载文件的地方改为下载压缩文件;b.调用算法前对压缩文件解压。以上2点改动后程序自动和原有逻辑保持一致,第一点变动无需多说,比较简单。第二点遇到了一些问题:
Plan A:Java解压失败:针对这种公开的算法以为百度一下,导入maven坐标就可以,结果失败了:
// https://blog.51cto.com/u_16175470/7944619
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>1.7.1</version>
</dependency>
Plan B:让运行同学在机器上装一个LZ4的指令,Java像调用tar指令一样调用是否可以呢?答案是:No
Plan C:跟算法同学沟通,让他们改算法,将Java传的文件先解压再执行逻辑,可以是可以,但没人(其实不想动);
Plan D:再次算法同学沟通,怀疑是压缩侧的同学使用的lz4组件不一样(他们是别的语言),所以对方提供了文档,得按着他们给的文档执行指令才可以:
#Decompress
lz4 -d file.lz4
算法更新,Java发版后进docker是可以执行的,但应用拼接指令发送后Linux底层不执行:
试了各种办法(https://blog.51cto.com/u_16213332/7535711)都不灵光:
有的说权限、有的说要把命令放到脚本中、在命令中加bash -c 我一一的试了个遍,结果都不行。如果是tar可以指定输入文件和输出路径,类似如下:
现在这个指令(lz4 -d file.lz4)只指定了输入,输出就是在当前目录,让对方升级下也不愿意。后来发现指令挂着时外面满屏的乱码:
完全看不懂,后来无意间发现在docker下执行时有个进度百分比,上面这些会不会也是执行的时候流输出,把流写到文件是不是就OK了,接着写如下代码:
String[] arr = new String[]{"lz4" ,"-d" ,"/data/xxx/input.lz4"};
File file = new File("/data/xxx/output.dat");
ProcessBuilder builder = new ProcessBuilder();
builder.redirectOutput(ProcessBuilder.Redirect.to(file));
//builder.redirectError(ProcessBuilder.Redirect.INHERIT);
builder.command(arr);
Process process = builder.start();
process.waitFor();
int code = process.exitValue();
log.info("builder exit code:{}",code);
至此,上述代码发现能在指令执行后成功的看到了对应目录下的目标文件,存在一个问题就是时间有点长,解压一个文件大约10秒左右,不过这已经成功了一大步,万一不能提升就这样先上线后续再优化。
继续思考有没有别的方式把这个流转文件,这个就太多了,不用自带的,可以自己读出来再写:
ProcessBuilder builder = new ProcessBuilder();
builder.command(cmd);
Process process = builder.start();
InputStream input = process.getInputStream();
OutputStream output = new FileOutputStream(outPath);
//这里构建数组大小为1024、2048或 input.available()对性能都有影响,好奇的可以试试
byte[] buffer = new byte[1<<24];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
output.close();
input.close();
process.waitFor();
这个比上面就快多了,实测下来1~2秒左右就能解压并完成写文件。到这里,本次节能降本Java侧的适配完成。
总结
为方便后续其它同学复用,对lz4的解压抽成工具类放到了commot tools中:
更好的做法其实应该是压缩侧的同学考虑要周全,一个新的指令如果能类似tar命令一样的通用,支持类似:lz4 -d input_file -o output_file,根本就不会这么麻烦(当然也没有这次的学习进步了)。
对于写文件时数组大小的定义,要多试几次:
InputStream input = process.getInputStream();
byte[] buffer = new byte[1<<24];
// ignore
这里构建数组大小为1024、2048或 input.available()对性能都有影响,好奇的可以试试。
官网:https://lz4.org/ 提供了各种语言相关的文档,压缩和解压都用同一语言应该没有问题。
P.S:把.lz4文件拷贝到docker容器测试用以下命令:
docker cp /xxx/xxx.lz4 ${docker_id or server_name}:/xxx/xxx.lz4
看到的同学觉得有用,点赞、关注、转发搞一个阔以不
前事不忘,后事之师。·
刘向《战国策·赵策一》