并发操作的文件名问题

最近我所参与的某一个项目进入了内测阶段。QA也只对现有系统进行功能性测试,性能测试还没开始做。在这个项目,有一个模块,主要功能是接收到用户的请求后,调用ImageService服务,生成一个明信片返回给用户。这个看似简单的业务逻辑,内部实现却不简单,其操作有获取用户的地理位置,从DFS下载邮戳模板文件,从DDB中获取邮戳模板元数据,根据用户的地理位置、请求时间生成邮戳,上传邮戳至DFS, 从DFS下载明信片模板文件,合并邮戳与明信片模板文件,生成明信片,上传明信片至DFS。当然,为了保证性能,这其中还包含不少的Cache处理等,才能保证每次请求都能在100ms左右响应。

由于,项目已经进入内测,而且bug主要是前端的bug,和我这个后端的开发没有关系,因而相对工作比较轻松。于是,我就写了个小的测试程序,其原理与《教你如何从Google Map爬数据》一文是一样的,就是开一个线程池,每一个线程的任务就是访问明信片服务。但是,当我把线程池的大小开到32时,也就是有32个线程在同时访问明信片服务时,总有那么几个请求是失败的。这一下让我冒出冷汗,开始查找请求失败的原因。

首先,我估计失败的可能,主要有这么几点:DDB请求失败、DFS下载文件失败、DFS上传文件失败、ImageService业务逻辑问题。因为之前出现过DFS由于ID分配失败,导致文件上传失败。

再次,查看系统日志。

并发操作的文件名问题_第1张图片

这是ImageService调用GraphicsMagick合并邮戳与明信片模板时报的错,原因为PNG unsigned integer out if range。Google、Baidu一番,不知道为何。先不管是具体怎么回事,无外乎由于邮戳文件有问题,明信模板文件有问题,或者GraphicsMagick的问题。看到这个异常,我基本上可以断定,请求失败的原因,不是DDB请求的问题了,也不是DFS文件ID分配失败的问题了。

先将那个文件下载下来看看吧。这时,发现了问题,在失败的请求中,邮戳文件的大小只有3B多,或者比这个略多一点。而明信片模板一直是正常的文件。看来问题不是出在合并邮戳与明信片模板那里,而是出现在邮戳生成。难道是邮戳上传失败?先不管有没有失败,先看看邮戳生成时的日志吧。

并发操作的文件名问题_第2张图片

日志内容表示日志生成与文件上传都是成功的啊。看了半天,终于发现了问题,如上图中,红线位置,这是邮戳文件在本地暂存的文件名。因为系统生成邮戳与生成明信片时,都必须将文件在本地暂存一下。我定义临时文件名时,文件名中,包含有操作名、源文件ID、时间戳,以保证同一时刻不会出现两个进程操作同一个文件。但是,有没有发现是相同的?这就是问题所在。我本以为生成邮戳、生成明信片时计算量很多,用操作名+源文件ID+时间戳足以对文件进行区分。但是没想到的是,在我设计的业务逻辑中,是先取文件名,再进行计算,最后才是输出到本地文件系统。也就意味着,我同时发出32个请求时,32个线程同时去取文件名,也几乎在同时去取时间戳,总会存在那么几个线程取到的时间戳是相同。于是,苦逼的问题来了。线程A与线程B取到了同像的时间戳,往同一个文件时输出结果,于是线程A先计算完成时,内容总是被线程B覆盖掉一部分,所以文件只有3B左右。由于,文件不完整,在合并图片时,抛异常了。

在同时可能存在多个线程操作文件系统时,怎么定义各自的文件名呢?我想,大致这么几种吧:

  1. 时间戳,但实际情况证明,这并不是很可靠;
  2. 在文件名中,加入线程名,线程名都是不一样的,而且同一时间,同一线程只能操作同一个文件,绝对安全;
  3. 线程维护一个static变量A,每次取文件名时,A++,并作为文件名的部分;
  4. uuid,不过,好长的一串啊;
后来,我采用了方法2,去除了文件名中的时间戳。在后来测试中,将并发提到128、256都正常(当然,此时不可能再出现这样的异常了)。
总结下吧。
  1. 时间戳来进行区分,在高并发的情况下,不一定可靠;
  2. 虽然,在实际线上环境中,这个问题出现在可能性很小,但也必须进行处理;
  3. 日志输出要规范,这对以后查找问题很有好处;
  4. 庆幸,这是在内测环境中。我开的log等级是Info,如果是线上环境,开了Warn,就不是那么查找了; 
  5. 越是简单的地方,越是容易出问题;
就说到这里吧,系统如果移至线上环境,我会将url发布出来,欢迎大家试用。

你可能感兴趣的:(工作,cache,测试,Google,Integer,任务)