1. Información general
Reimpreso: http://cxytiandi.com/blog/detail/15386
Reimpreso de: intercambio de tecnología de Xu Jingfeng Kirito
Se ha descubierto que kill -9
el script de reinicio revisó el proyecto reciente, la operación y el mantenimiento utilizan la forma de reinicio de springboot embebido de tomcat, de hecho, casi todos estuvieron de acuerdo en que: kill -9
la forma relativamente violenta, pero exactamente lo que traerá, pero pocas personas pueden analizar una pista. Este artículo registra principalmente mi propio proceso de pensamiento.
¿Cuál es la diferencia entre kill -9 y kill -15?
En el pasado, el paso habitual para publicar aplicaciones WEB era envolver el código en un paquete war y luego colocarlo en una máquina Linux equipada con un contenedor de aplicaciones (como Tomcat, Weblogic). En este momento, queremos iniciar / cerrar la aplicación de una manera muy diferente. Simple, simplemente ejecute el script de inicio / apagado en él. Springboot proporciona otra forma de empaquetar toda la aplicación junto con el servidor Tomcat integrado, lo que sin duda aporta una gran comodidad a la publicación de aplicaciones y también plantea una pregunta: ¿cómo cerrar la aplicación Springboot? Una forma obvia es encontrar la identificación del proceso según el nombre de la aplicación y eliminar la identificación del proceso para cerrar la aplicación.
La descripción del escenario anterior plantea mi pregunta: ¿cómo eliminar un proceso de aplicación Springboot con gracia? Estos son solo el sistema operativo Linux más popular, por ejemplo, es responsable del proceso de matar en el comando kill de Linux, después de lo cual puede ir seguido de un número que representa el número de señal (Señal), ejecutar kill -l
comandos, puede enumerar todo el número de señal.
xu@ntzyz-qcloud ~ % kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS
Este artículo presenta principalmente el 9º código de señal KILL y el 15º número de señal TERM.
Primero, simplemente comprenda la diferencia entre los dos:
kill -9 pid
Se puede entender que el sistema operativo mata a la fuerza un proceso desde el nivel del kernelkill -15 pid
Puede entenderse como el envío de una notificación para informar a la aplicación de que cierre activamente.
Esta comparación es todavía un poco abstracta, así que echemos un vistazo al rendimiento de la aplicación. ¿Cuál es la diferencia entre estos dos comandos para matar la aplicación?
Preparación de código
Dado que el autor tiene mucho contacto con springboot, comenzaré la discusión con una aplicación simple de springboot como ejemplo y agregaré el siguiente código.
1 Agregue una clase que implemente la interfaz DisposableBean
@Component
public class TestDisposableBean implements DisposableBean{
@Override
public void destroy() throws Exception {
System.out.println("测试 Bean 已销毁 ...");
}
}
2 Agregue ganchos cuando la JVM esté cerrada
@SpringBootApplication
@RestController
public class TestShutdownApplication implements DisposableBean {
public static void main(String[] args) {
SpringApplication.run(TestShutdownApplication.class, args);
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行 ShutdownHook ...");
}
}));
}
}
Pasos de prueba
Ejecute java -jar test-shutdown-1.0.jar
la
prueba en funcionamiento de la aplicación kill -9 pid,kill -15 pid,ctrl + c
después de
los resultados de la prueba de contenido de salida del registro
kill -15 pid & ctrl + c
, El efecto es el mismo, la salida es la siguiente
2018-01-14 16:55:32.424 INFO 8762 --- [ Thread-3] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2cdf8d8a: startup date [Sun Jan 14 16:55:24 UTC 2018]; root of context hierarchy
2018-01-14 16:55:32.432 INFO 8762 --- [ Thread-3] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
执行 ShutdownHook ...
测试 Bean 已销毁 ...
java -jar test-shutdown-1.0.jar 7.46s user 0.30s system 80% cpu 9.674 total
kill -9 pid
, No se generan registros de aplicaciones
Se puede encontrar que kill -9 pid
la aplicación fue tomada por sorpresa, sin dejar ninguna posibilidad de que reaccione. Por otro lado kill -15 pid
, son más elegantes, primero por la AnnotationConfigEmbeddedWebApplicationContext (一个 ApplicationContext 的实现类)
recepción del aviso, seguido de la implementación del código de prueba Shutdown Hook
, la implementación final del DisposableBean#destory()
método. Cual es mejor o peor, se hace el juicio.
Generalmente, nos ocuparemos de la lógica de "consecuencias" cuando se cierre la aplicación, como
- Cerrar el enlace de socket
- Limpiar archivos temporales
- Envíe un mensaje al suscriptor para informarle que se desconecte
- Notifique al proceso hijo que está a punto de ser destruido
- Liberación de varios recursos
- y muchos más
Y kill -9 pid
es un tiempo de inactividad del sistema analógico directo, el sistema pierde potencia, por lo que la aplicación es demasiado poco amigable, no use la cosechadora para recortar macetas de flores. En su lugar, se usa kill -15 pid
en su lugar. Si encuentra una práctica en particular :kill -15 pid
que no puede cerrar la aplicación, puede considerar usar el nivel de kernel kill -9 pid
, pero asegúrese de que después de la investigación las causas kill -15 pid
no se puedan cerrar.
¿Cómo maneja Springboot -15 TERM Signal
Como se explicó anteriormente, la aplicación springboot se puede cerrar sin problemas usando kill -15 pid. Podemos tener las siguientes dudas: ¿Cómo responde springboot / spring a este comportamiento de apagado? ¿Cerró Tomcat primero y luego salió de la JVM o en el orden inverso? ¿Cómo es que están relacionados?
Intente continuar con un análisis desde el inicio del registro, AnnotationConfigEmbeddedWebApplicationContext
imprima el cierre de comportamiento, vaya directamente al código fuente para averiguarlo, culminando en su padre AbstractApplicationContext
para encontrar el código clave:
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose();
if (this.shutdownHook != null) {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
}
}
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
LiveBeansView.unregisterApplicationContext(this);
// 发布应用内的关闭事件
publishEvent(new ContextClosedEvent(this));
// Stop all Lifecycle beans, to avoid delays during individual destruction.
if (this.lifecycleProcessor != null) {
this.lifecycleProcessor.onClose();
}
// spring 的 BeanFactory 可能会缓存单例的 Bean
destroyBeans();
// 关闭应用上下文&BeanFactory
closeBeanFactory();
// 执行子类的关闭逻辑
onClose();
this.active.set(false);
}
}
Para facilitar la composición tipográfica y la facilidad de comprensión, eliminé parte del código de manejo de excepciones en el código fuente y agregué comentarios relevantes. Cuando se inicializa el contenedor, ApplicationContext ya está registrado como Shutdown Hook, este gancho se llama método Close (), por lo que cuando ejecutamos kill -15 pid
when, la JVM que recibe la instrucción de cierre activa el Shutdown Hook, y luego mediante el método Close () para lidiar con algunos de los Secuelas. Medios de rehabilitación específicos que dependen completamente de la lógica doClose ApplicationContext (), incluida la destrucción de los comentarios de caché de objetos singleton mencionados, liberan el evento de cierre, el contexto de la aplicación y así cerrar, especialmente cuando ApplicationContext
la clase se logra AnnotationConfigEmbeddedWebApplicationContext
cuando el , También maneja cierta lógica de apagado del servidor de aplicaciones incorporado de tomcat / jetty.
Después de echar un vistazo a estos detalles dentro de springboot, deberíamos entender la necesidad de cerrar las aplicaciones con elegancia. Tanto JAVA como C proporcionan la encapsulación de Signal. También podemos capturar manualmente estas señales del sistema operativo. No introduciré demasiado aquí. Si estás interesado, puedes intentar capturarlas tú mismo.
¿Hay otras formas de cerrar correctamente la aplicación?
El módulo de resorte-arranque-arrancador-actuador proporciona una interfaz tranquila para un apagado elegante.
Agregar dependencia
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Agregar configuración
#启用shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
En producción, tenga en cuenta que este puerto debe tener permisos establecidos, como el uso con spring-security.
La ejecución curl -X POST host:port/shutdown
de las instrucciones se puede obtener tras el cierre exitoso de la devolución:
{
"message":"Shutting down, bye..."}
Aunque springboot proporciona dicho método, según mi conocimiento actual, no he visto a nadie usar este método para detener la máquina. El método pid kill -15 logra el mismo efecto, y se enumera aquí para la integridad del programa. .
¿Cómo destruir el grupo de subprocesos como variable miembro?
Aunque la JVM nos ayudará a recuperar ciertos recursos cuando se apague, si algunos servicios utilizan devoluciones de llamada asíncronas y tareas de sincronización, es probable que un manejo inadecuado cause problemas comerciales. Entre ellos, cómo cerrar el grupo de subprocesos es un problema típico.
@Service
public class SomeService {
ExecutorService executorService = Executors.newFixedThreadPool(10);
public void concurrentExecute() {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("executed...");
}
});
}
}
Necesitamos encontrar una manera de cerrar el grupo de subprocesos cuando la aplicación se apaga (la JVM se apaga y el contenedor deja de ejecutarse).
Plan inicial: no hacer nada. En circunstancias normales, esto no será un gran problema, porque la JVM está cerrada y será liberada, pero obviamente no logró las dos palabras que este artículo viene enfatizando, sí-elegante.
La desventaja del método uno es que las tareas enviadas en el grupo de subprocesos y las tareas no ejecutadas en la cola de bloqueo se vuelven extremadamente incontrolables. Después de recibir la instrucción de apagado, ¿salen inmediatamente? ¿O esperar a que se complete la tarea? ¿O es esperar un cierto período de tiempo antes de que se complete la tarea antes de cerrarla?
Mejora del esquema:
Después de descubrir las desventajas de la solución inicial, inmediatamente pensé en usar la interfaz DisposableBean, así:
@Service
public class SomeService implements DisposableBean{
ExecutorService executorService = Executors.newFixedThreadPool(10);
public void concurrentExecute() {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("executed...");
}
});
}
@Override
public void destroy() throws Exception {
executorService.shutdownNow();
//executorService.shutdown();
}
}
ThreadPoolExecutor se APAGARÁ después del apagado, no podrá aceptar nuevas tareas y luego esperará a que se complete la ejecución de las tareas que se están ejecutando. Esto significa que el apagado es solo un comando, y si se apaga o no depende del hilo en sí.
ThreadPoolExecutor maneja el apagado Ahora de manera diferente. Después de que el método se ejecuta, se convierte en el estado STOP y llama al método Thread.interrupt () en el subproceso en ejecución (pero si el subproceso no maneja la interrupción, no sucederá nada), por lo que No significa "cerrar inmediatamente".
Verifique el documento java de shutdown and shutdown Ahora, encontrará los siguientes consejos:
shutdown() :Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.Invocation has no additional effect if already shut down.This method does not wait for previously submitted tasks to complete execution.Use {
@link #awaitTermination awaitTermination} to do that.
shutdownNow():Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. These tasks are drained (removed) from the task queue upon return from this method.This method does not wait for actively executing tasks to terminate. Use {
@link #awaitTermination awaitTermination} to do that.There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. This implementation cancels tasks via {
@link Thread#interrupt}, so any task that fails to respond to interrupts may never terminate.
Ambos sugieren que necesitamos un awaitTermination
método de ejecución adicional , solo que la ejecución shutdown/shutdownNow
no es suficiente.
La solución final: refiriéndonos a la estrategia de reciclaje del pool de hilos en primavera, obtuvimos la solución final.
public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
implements DisposableBean{
@Override
public void destroy() {
shutdown();
}
/**
* Perform a shutdown on the underlying ExecutorService.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
* @see #awaitTerminationIfNecessary()
*/
public void shutdown() {
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
this.executor.shutdownNow();
}
awaitTerminationIfNecessary();
}
/**
* Wait for the executor to terminate, according to the value of the
* {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
*/
private void awaitTerminationIfNecessary() {
if (this.awaitTerminationSeconds > 0) {
try {
this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS));
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
Los comentarios se mantienen, se eliminan algunos códigos de registro y se presenta ante nuestros ojos una solución para cerrar con gracia el grupo de subprocesos.
-
Use el indicador waitForTasksToCompleteOnShutdown para controlar si desea terminar todas las tareas inmediatamente o esperar a que se complete la tarea y luego salir.
-
executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS));
Controle el tiempo de espera para evitar que la tarea se ejecute indefinidamente (como ya se enfatizó anteriormente, incluso shutdownNow no garantiza que el hilo deje de ejecutarse).
Estrategias de cierre más elegantes que requieren nuestro pensamiento
Como se mencionó en nuestra serie de artículos que analizan los principios de RPC, 服务治理框架
generalmente se considera el tema del cierre ordenado. La práctica habitual es 事先隔断流量
continuar 关闭应用
.
Un enfoque común es enviar el nodo de servicio 从注册中心摘除
y los suscriptores para recibir notificaciones, eliminar el nodo y, por lo tanto, cerrarlo correctamente;
En lo que respecta a las operaciones de la base de datos, puede utilizar la función ACID de la transacción para asegurarse de que no se produzcan datos anormales incluso si se detiene el bloqueo, por no mencionar la conexión normal;
Otro ejemplo es que la cola de mensajes puede depender del mecanismo ACK + la persistencia del mensaje o la garantía del mensaje de transacción; para los servicios con más tareas de sincronización, debe prestar especial atención al problema de cierre ordenado cuando se maneja sin conexión, porque este es un servicio de larga duración, en comparación con otros servicios. La situación es más susceptible al problema del tiempo de inactividad. Puede utilizar idempotencia y banderas para diseñar tareas cronometradas ...
El soporte de funciones como transacciones y ACK kill -9 pid
puede hacer que el servicio sea lo más confiable posible incluso en situaciones como tiempo de inactividad, cortes de energía, etc., y también debemos pensar en kill -15 pid
la estrategia de apagado en condiciones normales fuera de línea. Finalmente, agregue algo de comprensión del gancho de cierre de jvm al resolver este problema.
El gancho de cierre garantizará que la JVM se esté ejecutando hasta que finalice el gancho. Esto también nos aclara que si realizamos una operación de bloqueo cuando recibimos el comando kill -15 pid, podemos esperar a que se complete la ejecución de la tarea antes de apagar la JVM. Al mismo tiempo, también explica el problema de que algunas aplicaciones no pueden salir al ejecutar kill -15 pid. Sí, la interrupción está bloqueada.
Otros buenos artículos: https://blog.csdn.net/u18256007842/article/details/85330504