工作中的思考(20210603)
问题
遇到一个匪夷所思的问题,使用ffmpeg命令对音视频、图片等进行处理时,本地可以通过接口调用执行cmd返回一段视频,打包后发布到ECS和K8S多台机器后,在sit环境居然直接挂了。
思考
遂在日志中排查问题,将通过埋点获得的命令在k8s上执行,居然也成功了,从过程上看确实是由于这条命令该生成的文件没有生成,但服务器终端手动执行又执行成功,一模一样的命令通过终端执行和通过Java的exec执行会有区别吗?
按道理来说不会有区别,因为Java本身是一次编译到处执行,并不会受到跨平台的影响,本地也是直接路由到docker镜像上。
还有一种可能是线程池的问题,利用两个线程去跑两个异步任务,有没有可能这个线程被不明原因阻塞,而程序主线程继续执行,导致我们命令执行成功,但文件未写入到服务器中。
也有可能是waitFor方法失效,不过可能性不大。
相同的命令我们在几个环境测试,居然会有不同的结果。
1 | ffmpeg -nostdin -r 25 -y -i 1.jpg -vf "zoompan='1.25':x='if(lte(on,-1),(iw- |
环境 | 结果 |
---|---|
本地ffmpeg执行 | 成功 |
本地Unbuntu20.04执行 | 成功 |
本地启动项目,通过调接口执行 | 成功 |
项目部署到k8s后,在服务器端centos的终端执行 | 成功 |
项目部署到k8s后,通过接口调用 | 失败 |
解决
今天通过增加日志的分析参数,发现在执行视频放缩时没有找到对应的过滤器,而ffmpeg中ld指令是获取我们在st中的值,查阅官方文档发现我们并没有缺少libavuitl这个库。
而后我发现在执行**eq(ld(1),0)在中间多加一个空格eq(ld(1), 0)**,想到之前调试到Java的exec源码,顺便分析一下。
首先随便写个main函数,开始单步调试。
1 | public static void main(String[] args) { |
执行进来以后发现首先声明了一个StringTokenizer,然后通过空格拆分,但是考虑到本地Java执行成功,还要继续跟进源码,进入下一个exec函数。
exec函数执行了start函数,可以得到如下图所示:
这段源码写的很简练,也很精髓,利用Java的万类之王Object,直接克隆对象直接修改,效率很高,接下来就是判断命令是否为空,是否存在权限,是否有不合法字符。
现在还没有发现问题所在,继续向下走。
最后,我注意到使用了一个枚举,源码注释是”Represents a source of subprocess input or a destination of subprocess output”,明白我们的exec函数其实是创建了一个进程(Process),这个进程会处理我们的命令,通过返回两个数据流(正常流和ERROR流),将流进行合并返回结果,这里就不写了。
看样子应该是Java创建linux的进程(准确说应该是task_struct)和shell执行有一定出入,也有可能是ffmpeg的bug。
后来在ffmpeg社区中发现有人遇到这个问题,在linux环境下服务器端过滤器不能加引号,整条命令不能有空格,仅限打包到服务器后,其他没有任何问题。
结论
学习一些开源软件,可以去社区看看结果,撰写命令行不该像写程序一样加空格。