怎么简单的锁定文件夹
今天,我们将讨论使事情保持简单,愚蠢(KISS)和鲁棒性的设计价值之间,设计不足和过度设计之间的冲突。
我们正在编写一个批处理Java应用程序,需要确保在服务器上一次最多运行一个实例。 团队成员有一个很好的想法,那就是使用锁定文件,这确实有效并且对我们有很大帮助。 但是,最初的实现不是很健壮,由于对该死的应用程序拒绝运行和查找锁定文件进行故障排除,这使我们花费了宝贵的时间和昂贵的上下文切换。
正如Comoyo的ØyvindBakksjø最近解释的那样,软件工程师与纯粹的编码器的不同之处在于,不仅要思考和关心通过代码的快乐路径,而且还要考虑不快乐的情况。 优秀的工程师会考虑可能出现的问题,并尝试适当地处理它们,以便依赖于它们和其用户的代码可以更轻松地处理有问题的情况。 健壮性包括及早发现错误,以良好的方式处理错误以及提供有用和有用的错误消息。 另一方面,简单性[TBD:Hickey]是系统的关键特征。 花太多时间来制作防弹代码总是很容易,而不是将精力集中在对业务更有价值的地方。
过于简单的实现
最初的实现非常简单:
public class SimpleSingletonBatchJob {private static boolean getLock() {File file = new File(LOCK_DIRECTORY+File.separatorChar+Configuration.getGroupPrefix());try {return file.createNewFile();} catch (IOException e) {return false;}}private static void releaseLock() {File file = new File(LOCK_DIRECTORY+File.separatorChar+Configuration.getGroupPrefix());file.delete();}public static void exit(int nr) {releaseLock();System.exit(nr);}public static void main(String[] args) throws IOException {...if (! getLock()) { // #1 try to create lockSystem.out.println("Already running");return;}... // do the job (may throw exceptions)releaseLock(); // #2 release lock when done}
}
主要问题是,如果该应用程序失败或被终止,它将留下锁定文件,而下次它将拒绝并以无用的错误消息开头。 您将需要了解/阅读代码以了解如何解决问题。
有人认为,这样的失败和故意的失败只会很少发生,以致于使代码更健壮的努力是没有道理的。 但是,我们需要投入很少的精力来使代码更加友好和健壮,f.ex。 通过在错误消息中包括锁定文件路径并解释为什么可能存在锁定文件路径以及如何解决该问题(例如“如果应用未运行,则锁定是失败运行的残余,可能会被删除”)。 确保在失败时删除文件是一些琐碎的代码行,可以节省一些混乱和时间。 另外,值得一提的是使其更强大,从而不需要太多的人工干预–对您的操作人员很友好。 (我希望是你。)
更强大的实施
这是改进的版本,具有有用的错误消息,并在失败时删除锁:
public class RobustSingletonBatchJob {// Note: We could use File.deleteOnExit() but the docs says it is not 100% reliable and recommends to// use java.nio.channels.FileLock; however this code works well enough for usstatic synchronized boolean getLock() {File file = new File(LOCK_DIRECTORY, StaticConfiguration.getGroupPrefix());try {// Will try to create path to lockfile if it does not exist.file.getParentFile().mkdirs(); // #1 Create the lock dir if it doesn't existif (file.createNewFile()) {return true;} else {log.info("Lock file " + file.getAbsolutePath() + " already exists."); // #2 Helpful error msg w/ pathreturn false;}} catch (IOException e) {throw new RuntimeException("Failed to create lock file " + file.getAbsolutePath()+ " due to " + e + ". Fix the problem and retry.", e); // #3 Helpful error message with context (file path)}}private synchronized static void releaseLock() {File file = new File(LOCK_DIRECTORY, StaticConfiguration.getGroupPrefix());file.delete();}public static void main(String[] args) throws Exception {boolean releaseLockUponCompletion = true;try {...if (! getLock() {releaseLockUponCompletion = false;log.error("Lock file is present, exiting."); // Lock path already loggedthrow new RuntimeException("Lock file is present"); // throwing is nicer than System.exit/return}... // do the job (may throw exceptions)} finally {if (releaseLockUponCompletion) {releaseLock(); // #4 Always release the lock, even upon exceptions}}
}
改进之处:
- 如果不存在锁,则创建一个存储锁的目录(该锁不存在,并导致令人困惑的“已运行”错误消息)已经使我们感到痛苦
- 有用的错误消息“锁定文件<文件的绝对路径>已存在。” =>易于复制和粘贴int rm 。
- 有用的错误消息,其中包含文件路径和错误信息,当我们无法创建锁时(空间不足,目录权限不足等)。
- 将整个主程序包装起来进行尝试–最后,确保始终删除锁定文件
代码仍然不是完美的-如果您终止了该应用程序,则锁定文件仍将留下。 有多种方法可以解决该问题(例如,将应用程序的pid包含在文件中,在启动时不仅检查其是否存在,而且还应检查pid确实存在/是否为该应用程序),但是在处理时间方面和增加成本方面复杂性的确高于收益。
结论
KISS和鲁棒性都是重要目标,并且经常会发生冲突。 使您的代码变得比所需的更健壮会使其变得过于复杂,并浪费时间,并且机会成本(丢失)。 由于故障排除,使代码过于简单会花费您或它的用户大量时间。 要实现正确的平衡,需要经验并不断地寻求平衡。 如果您的团队无法达成共识,最好从一个简单的代码开始,并根据其实际的健壮性需求收集硬数据,而不是事先对其进行过度设计。 不要像我一样成为完美主义者,但也要对您的用户和开发者同好。 如果您可以毫不费力地使您的应用程序更强大,那就去做吧。 如果需要更多工作,请去收集数据以证明(或不需要)该工作。
翻译自: https://www.javacodegeeks.com/2013/09/simplicity-vs-robustness-demonstrated-on-lock-file-handling.html
怎么简单的锁定文件夹