prefacio
La capacidad del programador para localizar el problema es muy importante, si hay un problema en la línea de producción y la solicitud enviada por el cliente no ha sido devuelta después de 10 segundos, ¿cómo lo investigaría?
jstack
jstack es un comando muy poderoso que puede generar una instantánea del hilo del momento actual del proceso especificado.Antes de hablar sobre cómo usar jstack para verificar, necesitamos algunos conocimientos previos
Transiciones de estado de subprocesos
Expresar | ilustrar |
---|---|
NUEVO | En el estado inicial, el hilo está construido, pero no se ha llamado al método start() |
FUNCIONABLE | Estado operativo |
ESPERANDO | Estado de espera, ingresar a este estado significa que el subproceso actual debe esperar a que otros subprocesos realicen una acción específica (notificación o interrupción) |
TIMED_ESPERANDO | Estado de espera de tiempo de espera, volverá por sí mismo dentro del tiempo especificado |
OBSTRUIDO | Estado bloqueado, lo que indica que el hilo está bloqueado en el candado |
TERMINADO | Estado terminado, que indica que el subproceso actual ha completado la ejecución |
Después de conocer los estados anteriores, será mucho más fácil para nosotros entender la información de volcado de jstack.En la información de volcado, debemos prestar atención a los siguientes estados
- punto muerto, punto muerto
- Esperando recursos, Esperando en condición
- Esperando para obtener el monitor, Esperando la entrada del monitor (generalmente esto es un problema)
- bloqueado, bloqueado
- En ejecución, Runnable
- Suspendido
Simular espera en condición
public class ThreadSleepTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000000);
}
}
复制代码
Simular interbloqueo
public class ThreadDeadLockTest {
public static void main(String[] args) {
StringBuilder a = new StringBuilder();
StringBuilder b = new StringBuilder();
Thread thread1 = new Thread(() -> {
synchronized (a) {
a.append("a");
b.append("b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
b.append("c");
a.append("d");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (b) {
b.append("b");
a.append("a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
a.append("c");
b.append("d");
}
}
});
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
复制代码
Se puede ver que tanto el subproceso 1 como el subproceso 2 están esperando recursos de bloqueo y están en estado BLOQUEO
Mire más abajo, puede ver el indicador de interbloqueo
ejecutable simulado
public class ThreadRunningTest {
public static void main(String[] args) throws InterruptedException {
int sum = 0;
while (true) {
sum += 1;
}
}
}
复制代码
Cómo encontrar hilos problemáticos
Nuestro programa simula todo lo anterior, y el hilo del problema se puede encontrar fácilmente, pero en el entorno de producción real, cuando la solicitud es muy grande, ¿cómo encontrar el hilo del problema?
El método más clásico son los siguientes cuatro pasos, averigüe el subproceso que ocupa la CPU más alta e imprima la información de la pila
// 找出占用cpu最高的进程
top
// 找出该进程下占用cpu最高的线程
top -Hp pid
// 打印该线程的16进制数据
printf "%x\n" 线程id
// 打印堆栈信息
jstack pid | grep nid=16进制
复制代码
Time+: el tiempo acumulado que el hilo ocupa la CPU
También hay un método más ingenioso: después de volcar una pila de subprocesos, volcar una copia después de un período de tiempo.
Use Comparar archivos en IDEA para comparar y analizar las similitudes y diferencias entre los dos archivos de volcado. Si hay una solicitud que no ha regresado por más de 10 segundos, teóricamente existirá en ambos archivos. En este momento, puede averiguar el ID de hilo Es un método más clásico, y este método también se puede usar para solicitudes de captura de paquetes.
base de datos
Si la solicitud no ha sido devuelta por mucho tiempo, generalmente primero verifico la base de datos.Si la base de datos es penetrada, las consecuencias serán muy graves.
Ahora verifique si el registro de consultas lentas está habilitado
mysql> show variables like '%slow_query_log%';
+---------------------+----------------------------------------------------------+
| Variable_name | Value |
+---------------------+----------------------------------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /usr/local/mysql/data/zhangxiaobindeMacBook-Pro-slow.log |
+---------------------+----------------------------------------------------------+
2 rows in set (0.01 sec)
复制代码
Ejecutar un sql lento
mysql> select sleep(100), id from user where id = 1;
+------------+----+
| sleep(100) | id |
+------------+----+
| 0 | 1 |
+------------+----+
1 row in set (1 min 40.24 sec)
复制代码
Podemos consultar este sql en el registro de consultas lentas y saber cuánto tiempo tardó en ejecutarse
/usr/local/mysql/bin/mysqld, Version: 5.7.27-log (MySQL Community Server (GPL)). started with:
Tcp port: 3306 Unix socket: /tmp/mysql.sock
Time Id Command Argument
# Time: 2021-07-03T08:33:40.680324Z
# User@Host: root[root] @ localhost [] Id: 7
# Query_time: 100.238479 Lock_time: 0.168766 Rows_sent: 1 Rows_examined: 1
use test;
SET timestamp=1625301220;
select sleep(100), id from user where id = 1;
复制代码
Después de encontrar el sql, es fácil de hacer. Puede usar la palabra clave de explicación para ver el proceso de ejecución del sql
mostrar lista de procesos
También es posible que el sql no haya ingresado al registro de consultas lentas. En este momento, puede usar el comando show processlist para verificar si hay algún sql que tarde demasiado en ejecutarse y luego localizar el sql problemático más rápidamente.
mysql> show processlist;
+----+------+-----------------+------+---------+------+------------+----------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+------+---------+------+------------+----------------------------------------------+
| 2 | root | localhost:54718 | test | Sleep | 35 | | NULL |
| 3 | root | localhost:54725 | NULL | Sleep | 42 | | NULL |
| 4 | root | localhost:51794 | test | Sleep | 62 | | NULL |
| 5 | root | localhost:51795 | NULL | Sleep | 12 | | NULL |
| 7 | root | localhost | test | Query | 23 | User sleep | select sleep(100), id from user where id = 1 |
| 9 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------------+------+---------+------+------------+----------------------------------------------+
6 rows in set (0.00 sec)
复制代码
Referencias
"Programación Java Concurrente"