在JAVA中如何实现长时间任务
摘要:在软件开发中,我们经常面临着处理长时间任务的源码多线程编程问题。在我们的源码ezOne平台的开发中就多处涉及到,如JPC数据服务JPC数据处理服务、源码报警联动、源码指标源码怎么安装门禁系统等。源码本人在编写DEMO程序的源码过程中几易其稿,煞费心机,源码但依然感觉有许多地方需要改进,源码为了减少多线程编程带来的源码风险,我尝试翻译整理了一个类似问题的源码解决方案框架以达到一劳永逸。 为了便于阅读,源码保留原文。源码 一、源码问题背景Quite often, you need a class to perform tasks like data processing, listening to events, or checking another class' activities during the application’s lifetime. To achieve this, you probably use threads with a set of locks and notifications. Java Thread API is well documented, but you need a great deal of code and experience to make your thread work properly and efficiently. You can avoid writing such classes from scratch every time you need them and build a more robust application by applying the framework we'll discuss in this article. 在应用程序中我们经常需要一个类去完成像数据处理、监听事件或检查另一个类的活动等任务。为了达到这个目标,我们可能使用带有一套锁和消息通知的线程。JAVA 线程API已经很好的文档化,但为了使线程能够正确而高效地运行,程序员仍然需要丰富的编程经验并编写大量的代码。通过应用本篇文章中讨论的框架,程序员能够避免忍受煎熬写大量的代码,快速创建健壮的应用程序。 二、长时间运行任务的程序框架Framework for long-running tasks The primary thing about a long-lived task is that it should somehow be kept running during the application lifetime. The right way to accomplish this is to provide a thread of execution for a particular task. You create a task as a thread or as an implementation of the java.lang.Runnable interface. If you implement Runnable, you can gain better object-oriented design and avoid the single-inheritance problems. You can also more efficiently manipulate with Runnable instances, for example, using a thread pool that usually needs a Runnable instance, not a thread, to run. 关于长时间运行的任务的主要事情是如何在应用程序的生命期使它一直保持运行。实现的恰当方法是提供一个线程来执行这个特定的任务。我们可以通过继承Thread类或实现java.lang.Runnable接口来达到该目标。如果采用实现Runnable接口的方式,就可以能够获得更好的源码图片怎么制作面向对象的设计,同时可以避免JAVA中的单继承问题。另外,我们也能更有效的处理Runnable实例(例如使用线程池通常需要一个Runnable实例而不是线程来运行)。 The essence of the framework is the abstract class Worker ( Listing A), which implements the Runnable interface and provides the helper methods for efficient task handling. Some of the methods are fully implemented, like the run() method, but some are abstract and have to be filled by you. If you want to create a long-running class, you need only to extend the Worker class and implement several abstract methods. Let’s look at these methods in more detail. 框架的基础是一个叫Worker的抽象类,它实现了Runnable接口,并提供了有效处理任务的好方法。这些方法有些已经被实现,如run()方法,但有些是抽象方法,开发人员必须自己来实现。如果要创建一个长时间运行的类,你只需要继承Worker类并实现几个抽象方法。让我们看看这些方法的细节。 The run() method of the Worker class is designed to continuously execute the work() method until it is stopped. The work() method can be responsible for data processing, reaction to some event, file reading or writing, SQL execution, etc. It can throw an exception, so it is a good practice to propagate it and let the run() method handle it. Worker 类的run()方法被设计成只要不停止运行就持续的执行work()方法。work()方法可以负责数据处理、事件响应、文件读写、,执行SQL命令等操作。这样work()方法能够抛出异常,并将异常传给run(),然后由run()方法来处理这些异常。 The run() method has two levels of try-catch clause: outside and inside the while-loop. The first try-catch clause is meant to catch all nonprogrammed exceptions and guarantee that the run() method never exits. The second clause will catch any kind of exceptions belonging to business logic and behave accordingly. If some waiting operation takes place in the work() method (e.g., waiting on an InputStream or a Socket), it is advisable to propagate an InterruptedException. The thing to keep in mind is that the work() method does not need to have any while-loop to keep it going as long as an application runs. The Worker does this for you. run()方法有内外两层try-catch语句:一层处于while-loop循环外,一层在while-loop循环内。前一个try-catch用于捕获非编程异常以确保run()方法不退出。后一个try-catch语句捕获关于业务逻辑和相应行为的各种异常。如果在work()方法中发生了一些等待操作(例如等待一个输入流或一个Socket),抛出一个InterruptedException的方法是可取的。要记住的是只要应用程序在运行,work()方法不需要任何while-loop循环去维持它运行,clion构建tensorflow源码这一切由Worker代办了。 When the run() method starts, it calls the prepareWorker() which is designed to prepare all resources needed for a long-running task (Listing A). In this method call, you can, for example, establish a database connection or open a file that will be used further. It is especially good to place here some blocking operations like opening a socket, because they will be done in a separate thread and thus will not block the main thread of execution. run()开始时,调用prepareWorker()方法来准备长时间运行任务需要的所有资源(参考程序清单A)。例如 ,在这个方法中可以打开一个将要用到的数据库连接或文件。尤其对于那些像建立一个socket这样的阻塞操作放在这儿是很好的。因为若让它们在一个独立的线程中运行,则不会阻塞主线程的执行。 The opposite of the previous method is the releaseWorker() which is called when the run() method is about to exit (Listing A). Here, you can put the code to dispose of system resources used by this task or to perform other cleanup. This method is similar to java.lang.Object.finalize(), but it is explicitly called before a thread terminates. 与前面方法相反的是releaseWorker(),它在run()方法准备退出时被调用(参考程序清单A)。在该方法中你可以编写那些释放系统资源或执行其它清除动作的代码。该方法类似于java.lang.Object.finalize(),但它在线程中止时被显式的调用。 三、框架中的错误处理机制Handling errors in the framework Another important method is the handleError(), which takes a java.lang.Throwable as a parameter. This method is called each time an error situation occurs within the run() method. It is up to you how to implement error handling. One way is to log errors and control task termination by calling halt() method (Listing A). 另一个重要的方法是handleError(),它带有一个java.lang.Throwable的输入参数。在run()方法每次发生错误时调用这个方法。这依赖于你怎么实现错误处理。方法之一是写错误日志并通过调用halt()方法中止任务(参考程序清单A)。 The isCondition() method is used to tell whether execution of the work() method can be started, thus allowing granular control over a task. It is useful in event-triggered frameworks when execution of the work() method is pending until some condition?for example, a buffer is not empty?is fulfilled. In Worker’s implementation, the condition is checked upon a lock notification and periodically with a time interval you specify in the setTimeout() method (Listing A). If you don’t need any waiting blocks in a task, just make the isCondition() method always return true. isCondition()方法用于判断work()方法是否能够被执行。因此允许细粒度地控制任务。这在事件触发的框架中非常有用。当work()方法的执行条件未满足时,work方法将被挂起,直到条件完全满足(例如,缓存区非空)。在Worker的实现中这个条件将按在方法setTimeout()中指定的时间周期地检查一个锁通知。如果在任务中不需要任何等待阻塞,仅仅只要使isCondition()方法总是返回真值。 四、中短共振指标源码任务终止时机When to terminate You'll also need the isRunning(), broadcast(), and halt() methods. Querying isRunning(), you can check whether a task is still running and make a decision whether to terminate it. The broadcast() method just notifies the lock object and makes a task proceed if it has been waiting on this lock. The halt() method stops a task, so the run() method will exit as soon as the next isRunning() status is checked. Because this method notifies only one lock that may block this task’s thread, it is advisable to use the same lock object when you do blocking operations within the work() method ( Listing B). If you can't use the same lock object, such as when you're blocking on the java.io.InputStream.read() method, you should add explicit notification of all possible locks or add java.lang.Thread.interrupt() to your halt() method. The java.lang.Thread.interrupt() works if an object you are blocked on processes this signal correctly. For example, it works for InputStream.read() but doesn’t work for java.sql.PreparedStatement.execute(), so you have to test halt() method in each particular situation. 你还需要isRunning(), broadcast(), halt()方法。通过访问isRunning()方法,你将能检查某个任务是否正在运行,并决定是否中止它。broadcast()方法正确地通知锁对象,并且如果这个对象一直等待这个锁,那么就激活这个任务。halt()方法中止一个任务,因此下一isRunning()状态一旦被调用,run()方法就退出,因为这个方法只通知那个可能阻塞这个任务线程的锁。当在work()方法中执行阻塞作业时用相同的锁是明智的。如果你不能用相同的锁对象时,例如在执行java.io.InputStream.read()方法遇到阻塞时,你就应该添加所有可能锁的显式通知或者增加java.lang.Thread.interrupt()到halt()中。如果一个你阻塞的对象被正确处理,java.lang.Thread.interrupt()将会起作用。例如,它在InputStream.read()执行时有作用,但在执行java.sql.PreparedStatement.execute()不起作用,因此在每个特殊的条件下你必须测试halt()方法。 Once you are familiar with the Worker class, you can easily create your own implementation (Listing B). To run this class as a thread, simply use a new Thread(new WaitedWorker()).start. Applying Thread.interrupt() or Worker.halt() or a combination of them, you can control task execution precisely. For example, you can stop all workers when JVM shuts down by placing corresponding code in the java.lang.Runtime.addShutdownHook() method. 一旦你熟悉Worker类,你就很容易创建你自己的实现(参考程序清单B),为了把这类当作一个线程运行,仅仅只需简单地使用 new Thread(new WaitedWorker()).start。应用Thread.interrupt()或Worker.halt()或它们的组合,你就可以准确的控制任务的执行。例如当JVM通过在java.lang.Runtime.addShutdownHook()方法中放相应的代码停止时,你就能停止所有的任务。 四、变号器源码结论Conclusion We've examined the long-running task framework and seen how to create new tasks based on its abstract class. Its architecture is clear and flexible and was designed with extensibility in mind. With this framework, you can avoid creating classes from scratch, and you'll be able to develop more efficient and reliable applications. 我们已经检查了长时间运行任务框架,并且看到怎样通过从创建一个基于它的抽象类的任务。它的构架是清晰和灵活的,并且被设计成可扩展的。用这个框架你能避免为创作类而绞尽脑汁,并且帮助你能够开发出高效、可靠的应用程序。 参考文献1、<> Simon Brown 2、<> Joseph L.Weber 致谢非常感谢曾一起工作的同事和为JAVA技术发展做出贡献的人们,没有他们的努力成果,我就不可能感受到JAVA语言的魅力;同时也要感谢我的上司周洪波博士和卢盛融总工程师,他们给予我许多有益的帮助。 关于译者胡继锋,软件工程师,清华同方应用信息系统本部ezONE研究院研发人员。曾经从事DEPHI、VB、VC、C、PHP、ASP、Oracle等的开发,现在热衷于JAVA技术。同时对资本市场有一定的兴趣。
Dubbo 优雅停机演进之路
一、前言
在深入探讨 Dubbo 实现优雅停机的机制之前,我们需要理解 Java 实现优雅停机的原理。接下来,我们将详细研究 Dubbo 如何在不同版本中解决优雅停机的实现问题。
二、Dubbo 优雅停机待解决的问题
为了实现优雅停机,Dubbo 需要解决三个核心问题:确保新的请求不再发往正在停机的服务提供者,注销注册中心以通知消费者下线服务,以及关闭与客户端的连接,以避免正在进行的服务请求被忽略。
三、2.5.X 版本实现
在 Dubbo 2.5.X 版本中,优雅停机实现较为完整且易于理解。该版本的核心在于使用 AbstractConfig 静态代码注册一个 ShutdownHook,并在应用停机时调用 ProtocolConfig.destroyAll() 方法来执行主要的优雅停机流程。
3.1、优雅停机总体实现方案
优雅停机的入口类位于 AbstractConfig 静态代码中,其作用是注册一个 ShutdownHook,确保一旦应用停机,将调用 ProtocolConfig.destroyAll() 方法来销毁所有协议配置。
3.2、注销注册中心
注销注册中心是优雅停机实现中的关键步骤。这通常通过调用 AbstractRegistry 的相关方法来完成,具体操作包括从注册中心删除服务节点、取消订阅,以及最后关闭与注册中心的连接。
3.3、注销 Protocol
在关闭服务时,Dubbo 会处理两种 Protocol 子类:用于服务端请求交互的 DubboProtocol 和用于内部请求交互的 InjvmProtocol。主要关注 DubboProtocol 的内部逻辑,包括关闭 Server 和 Client 的具体操作。
3.4、关闭 Server
关闭 Server 的过程涉及向服务消费者发送 READ_ONLY 事件,确保消费者排除掉停机的服务节点。然后关闭心跳检测和底层通讯框架 NettyServer,以降低服务被消费者调用的风险。
3.5 关闭 Client
关闭 Client 的流程与关闭 Server 类似,重点关注处理已经发出请求的逻辑,确保所有请求都得到响应。
四、2.7.X 版本改进
随着 Spring 框架的使用,2.5.X 版本的停机过程可能导致优雅停机失效。为解决这一问题,Dubbo 从 2.6.X 版本开始重构相关逻辑,并最终在 2.7.X 版本中解决了“双 hook”问题,通过引入 ShutdownHookListener 监听 Spring 关闭事件,确保优雅停机流程不受 Spring 关闭事件的影响。
五、总结
实现优雅停机看似简单,但其中细节复杂,任何一个环节出现问题都可能导致优雅停机失效。学习和参考 Dubbo 的实现逻辑,可以为解决相关问题提供宝贵经验。Dubbo 系列文章和资源提供了深入的见解,帮助你更全面地理解 Dubbo 的优雅停机机制。
Java ShutDown Hook介绍和实例
Java的ShutDown Hook机制允许开发者在Java虚拟机(JVM)即将关闭之前执行一些清理或终止操作。这种机制提供了一个钩子,让开发者在JVM关闭时捕获关闭事件并执行相应的逻辑。在源码中,主要涉及两个类:ApplicationShutdownHooks和Runtime。
添加Hook实际上是在ApplicationShutdownHooks的静态Map中放入新的线程。这些线程在程序退出时被运行,每个带有Hook任务的线程的start()方法才被执行。由于Hook之间是独立的线程,它们的执行顺序没有关系。主线程调用每个Hook线程的join方法,确保主线程等待所有Hook执行完毕后退出。
有一些情况无法添加Hook:1. ApplicationShutdownHooks已经在调用Hook时,hooks会被置为null,无法添加新Hook。2. Hook的Thread不能是已经在运行状态的线程。3.因为储存的Hook是根据线程是否相同来判断的,所以不能添加相同的Hook。
ShutDown Hook不适用于处理非正常退出的情况,如kill -9命令。同时,使用ShutDown.halt和kill -9一样都是强制退出,不会给Hook执行的机会。
下面是一个简单的ShutDown测试例子。通过GitHub地址可以找到这个例子。
ShutDown的使用相对简单,网上有很多关于Spring和Dubbo等开源框架使用ShutDown Hook的例子,主要应用于资源释放和清理工作。
需要注意的是,Hook的执行顺序是无序的,不能重复添加相同的Hook,且已经执行的Hook不能再创建新的Hook。
在平时的应用中,ShutDown Hook的使用频率较低。一个有用的场景是在JVM挂掉时,Hook中可以给监控程序发送通知,如发邮件等,以便技术人员进行处理。
关于ShutDown Hook的相关资料,可以参考Oracle官网资料、Java Shutdown Hook的场景使用和源码分析、以及Adding Shutdown Hooks for JVM Applications等。
求你别再用kill -9了,这才是SpringBoot 的优雅停机方式...
微服务架构中,服务的优雅上下线是确保系统稳定性和用户体验的重要环节。本文旨在探讨如何在SpringBoot中实现优雅的停机和上线策略,以及在不同部署环境如Docker和外置容器下如何实践。
### 基础下线
在Spring/SpringBoot应用中,优雅停机可以通过JVM的shutdownHook机制实现。此机制支持在程序正常退出、使用System.exit()、终端Ctrl+C操作或直接使用Kill命令时执行优雅停机流程。然而,如果使用了Kill -9命令,程序将无法进行任何优雅停机操作。
SpringBoot为开发者提供了默认的shutdownHook支持,响应诸如Ctrl+C或kill - TERM信号。启动应用后,通过Ctrl+C操作,日志中将出现疑似“Closing...”的输出,这由`AnnotationConfigEmbeddedWebApplicationContext`类中的逻辑实现。开发者可以通过监听`ContextClosedEvent`事件,扩展自定义的下线逻辑,比如从注册中心注销服务。
### 微服务下的优雅下线
微服务通常在注销服务后才执行停机操作,那么在流量未被完全处理前,如何避免新请求进入?建议在注销服务后立即启用请求挡板,利用微服务框架的故障转移功能处理被拒绝的流量。
### Docker中的优雅下线
Docker提供了优雅关闭容器的命令,如`docker stop`和`docker kill`。`docker stop`会发送SIGTERM信号,并等待容器正常退出,最长等待秒,随后发送SIGKILL信号强制关闭。此过程给予容器足够的时间执行优雅停机逻辑,程序响应kill -信号即可。
对于等待时间长于默认秒的情况,可以通过`docker stop -t`命令自定义等待时间。
### 外置容器的优雅停机与上线
对于使用外置容器部署的场景,如Jetty,优雅停机与上线的实现依赖于RPC框架的优雅上下线接口。通过框架提供的接口,可以执行整个应用的生命周期结束逻辑,并自定义服务下线与上线的业务逻辑。在Jetty或其他外置容器的shutdown脚本中,可以封装调用此接口的逻辑,确保在容器停止或启动后,按照特定的流程执行下线和上线操作。
### 总结
在SpringBoot中,通过利用JVM的shutdownHook机制和框架提供的默认shutdownHook支持,可以实现优雅的停机流程。而对于Docker和外置容器,通过合理利用相关命令和框架接口,同样可以实现微服务的优雅上下线。关键在于结合应用需求,选择合适的部署环境和策略,确保服务在上线与下线时的平稳过渡。
2024-12-28 23:51
2024-12-28 23:27
2024-12-28 22:59
2024-12-28 21:59
2024-12-28 21:40