Práctica de gobernanza de bloqueo de aplicaciones de JD Financial

I. Introducción

A principios de 2020, la escala de usuarios de la aplicación JD Financial ha superado con creces la de hace unos años, y la actividad diaria también se ha duplicado. Al mismo tiempo, también hemos notado que la tasa de fallas de la aplicación ha aumentado ligeramente. con la iteración de la versión. Cuando nos dimos cuenta de que la falla de la aplicación había afectado la experiencia diaria del usuario, la tasa de fallas había llegado a unos pocos por mil. La tasa de fallas es un indicador importante para medir la calidad de la aplicación, que no solo afecta la estabilidad de la aplicación, sino que también afecta directamente la experiencia del usuario y el crecimiento del negocio. Si se produce un bloqueo durante el inicio, es posible que la aplicación se desinstale directamente, lo que provocará una disminución del valor de la marca y del boca a boca. Por lo tanto, si bien la aplicación financiera se desarrolla rápidamente, también presta más atención a la construcción de calidad.

La creciente tasa de accidentes de JD Financial App es inseparable del rápido desarrollo del negocio de aplicaciones. Los escenarios comerciales cada vez más complejos, el acoplamiento lógico entre múltiples negocios y la expansión de las funciones de la aplicación hacen que los programas sean más propensos a errores. Algunos códigos antiguos se han visto afectados lentamente después de múltiples iteraciones comerciales, y los errores en algunos escenarios especiales toman mucho tiempo y solo aparecen cuando una gran cantidad de usuarios los usan, lo que hace que la reparación del error sea menos oportuna. El problema de bloqueo que aparecía en la escala de grises se convirtió en un estado de espera para ser observado después de que fallara la búsqueda, y se volvió prominente cuando la cantidad de usuarios aumentó significativamente después de conectarse. La lenta acumulación de bloqueos hizo que la tasa de bloqueo se convirtiera en un número muy evidente en una determinada versión. Con base en esta situación, el equipo decidió internamente manejar a fondo la situación en ese momento y encontrar una manera de mantenerla.

La gestión de bloqueos de la aplicación JD Financial duró varias versiones, y los 20 principales bloqueos fueron básicamente reparados. Sin embargo, la reparación del accidente no es todo fácil, algunos problemas que son difíciles de reproducir se solucionarán después de reparar, observar y reparar. Durante el período de reparación de los problemas originales, el negocio de la aplicación también se actualiza continuamente y trae algunos problemas nuevos. El equipo de I + D presta especial atención a los problemas emergentes y utiliza la etapa de lanzamiento en escala de grises para eliminar los problemas de raíz. Al final, la aplicación financiera estabilizó la tasa de fallas por debajo de uno en 10,000.

Según el informe de experiencia de desempeño de la industria móvil de 2020, la tasa promedio de fallas de la industria de aplicaciones es 0.29%, la tasa promedio de fallas de la industria del lado de Android es 0.32% y la tasa promedio de fallas de la industria de aplicaciones del lado iOS es 0.10 %

inserte la descripción de la imagen aquí
La aplicación JD Finance se ha sometido a reparaciones continuas de alta calidad, y la tasa de fallas es dos órdenes de magnitud más baja que el promedio de la industria, y se ha mantenido estable en 0,007 % durante mucho tiempo.

Los datos de la tasa de fallas del usuario provienen del sistema de monitoreo de rendimiento de APM.
inserte la descripción de la imagen aquí
La tasa de accidentes de JD Financial App es muy superior al nivel de la industria, lo cual es inseparable de la exploración técnica en profundidad del equipo de I + D. El conocimiento básico de los accidentes es un requisito previo para la exploración técnica. Este artículo explicará el conocimiento básico de los choques desde superficiales a profundos y compartirá el proceso de solución de casos típicos de choques.

2. Definición de choque

1. El motivo del accidente

Un bloqueo es una respuesta explícita de la CPU a una excepción, y el manejo de excepciones de la CPU se basa en interrupciones. Interrupción significa que la CPU suspende el programa en ejecución, guarda la escena y luego ejecuta el programa de procesamiento correspondiente, después de procesar el evento, regresa al punto de interrupción y continúa ejecutando el programa "interrumpido".

Introducido en la información relevante del sistema operativo: interrupción (interrupt) y excepción (excepción) tienen diferentes significados en diferentes arquitecturas de CPU.

  • Por ejemplo, en la arquitectura Intel, la entrada de procesamiento de interrupciones se define mediante la tabla de despacho de interrupciones (IDT) en el kernel del sistema operativo. Hay 255 vectores de interrupción en la IDT, de los cuales los primeros 20 se definen como entradas de procesamiento de excepciones. es decir, la interrupción contiene una excepción.
  • En la arquitectura ARM, la entrada de procesamiento de interrupciones está en el vector de excepción (exception vector), y 3 de los 8 vectores de excepción están relacionados con interrupciones, es decir, las excepciones incluyen interrupciones.

Independientemente de cómo se definan las interrupciones y las excepciones, cuando ocurre una excepción en la CPU, transferirá el control del programa anterior a la excepción al controlador de excepciones, y la CPU no obtendrá derechos de ejecución inferiores. Cambiará al modo kernel y ejecutará el controlador de excepciones correspondiente. El ciclo de vida de una instrucción en una canalización clásica de CPU de cinco etapas es [obtener, decodificar, ejecutar, acceder a la memoria, reescribir], y pueden ocurrir excepciones de CPU en cada etapa, como en la arquitectura ARM:

  • Excepción de "aborto de datos" generada en la fase de "ejecución": si la dirección de la instrucción de acceso a datos del procesador no existe, o la dirección no permite el acceso de la instrucción actual, se genera una excepción de aborto de datos.
  • Anomalía "Prefetch abort" generada en la fase de "instrucción de búsqueda": si la dirección de la instrucción precargada del procesador no existe, o la dirección no permite el acceso de la instrucción actual, la memoria enviará una señal de cancelación al procesador, pero cuando se ejecuta la instrucción precargada, se genera una excepción de anulación de la precarga de instrucción.

Los controladores correspondientes a las dos excepciones llamarán directa o indirectamente a la funciónException_triage() del kernel de Mach y pasarán EXC_BAD_ACCESS como parámetro de entrada, yException_triage() usará el mecanismo de paso de mensajes de Mach para entregar la excepción.

2. ¿Cómo ocurre el bloqueo en el sistema iOS?

En el kernel del sistema iOS (Mach), las excepciones se manejan a través de la configuración básica "mecanismo de paso de mensajes" en el kernel. La excepción no es más complicada que un mensaje. La excepción es lanzada por el hilo y la tarea incorrectos a través de msg_send(), y luego manejado por un El programa captura a través de msg_recv(). Un controlador puede manejar la excepción, puede limpiar la excepción y puede decidir terminar la aplicación.

Para la aplicación, cuando la aplicación intenta hacer algo que no está permitido, como que la CPU no puede ejecutar ciertos códigos (acceder a memoria no válida, modificar el área de almacenamiento de solo lectura, etc.), o activa ciertas políticas del sistema operativo ( alto uso de memoria, el tiempo de inicio de la aplicación es demasiado largo, etc.), el sistema operativo protegerá la experiencia del usuario al cerrar su aplicación.

En algunos lenguajes de desarrollo, algunos objetos de programación detendrán la ejecución del programa y fallarán cuando encuentren errores. Por ejemplo, acceder a una matriz fuera de los límites en Object-C/Swift, NSArray/Array provocará un bloqueo y detendrá la ejecución del programa.

2. Varios tipos de accidentes comunes

1. puntero salvaje

Un puntero salvaje apunta a una dirección de memoria incierta y pueden ocurrir varias situaciones inciertas al acceder a esta dirección de memoria a través de un puntero salvaje. Si esta dirección de memoria no está cubierta, no necesariamente habrá un problema, si ha sido cubierta o asignada como un espacio inaccesible, el programa se bloqueará directamente. Si se considera que el bloqueo se debe a punteros salvajes, entonces el código de bloqueo actual probablemente no sea la causa del bloqueo, y la causa real del bloqueo debe encontrarse analizando la relación de llamada.

En el lenguaje C, los punteros salvajes a menudo ocurren cuando el valor inicial (dirección aleatoria) no se asigna después de que se declara la variable o el puntero no se vacía después de la liberación. por el subproceso actual es accedido por otro subproceso. El valor predeterminado del puntero en Object-C es nil, que es lo mismo que NULL en el lenguaje C, lo que significa que el puntero no apunta a ningún espacio de memoria. Los resultados de los errores de puntero salvaje suelen ser variables o excepciones de acceso a la memoria, y el tipo de bloqueo común es EXC_BAD_ACCESS errores de memoria.

2. punto muerto

En el sistema iOS, el uso de dispatch_sync para ejecutar tareas de sincronización en el subproceso principal provocará un punto muerto. Si la tarea se ejecuta en el subproceso principal (código de ejemplo a continuación) debido a algunos escenarios de lógica empresarial complejos, provocará que la aplicación se bloquee.

-(void)sceneAnalysis { 
  dispatch_sync(dispatch_get_main_queue(), ^{ 
    NSLog(@"Sync Task Result"); 
  }); 
  NSLog(@"Do Other Tasks"); 
}

El programa se atascará en la primera línea del cuerpo de la función y el mensaje de error es el siguiente: Subproceso 1: EXC_BAD_INSTRUCTION (código=EXC_I386_INVOP, subcódigo=0x0)

El código de ejemplo anterior es el interbloqueo más típico del subproceso principal. Se agrega una tarea de sincronización "NSLog(@"Sync Task Result")" a la cola principal, por lo que el subproceso principal suspenderá el código actual para ejecutar el bloque de código de bloque. y espere a que la función dispatch_sync Continúe la ejecución después de regresar. Pero la cola principal (main queue) es una cola en serie que sigue el principio de primero en entrar, primero en salir.Actualmente, el subproceso principal está ejecutando la función sceneAnalysis, y dispatch_sync necesita esperar a que se complete la función sceneAnalysis. sceneAnalysis y dispatch_sync se esperan el uno al otro, lo que provoca un interbloqueo.

3, perro guardián

Si la aplicación tarda mucho tiempo en realizar una determinada tarea (iniciar, finalizar o responder a eventos del sistema), el sistema operativo finalizará el proceso actual. El signo más evidente de un bloqueo provocado por el mecanismo de vigilancia es el código de error: 0x8badf00d. Por lo general, el registro de fallas se verá así:

inserte la descripción de la imagen aquí
Cuando el tiempo de inicio de la aplicación supera el valor máximo permitido (generalmente 20 s), el mecanismo de vigilancia se activará en el sistema iOS para finalizar el proceso de inmediato. Vale la pena señalar que el bloqueo desencadenado por el perro guardián no se recopilará en el control de errores desarrollado por él mismo, y el registro de bloqueo se puede obtener en el dispositivo de bloqueo. Apple desactiva el mecanismo de vigilancia cuando se usa el simulador y el mecanismo de vigilancia no se activará en el modo de depuración. Por lo tanto, en el proceso de desarrollo, el proceso de inicio de la aplicación debe ser conciso y cargarse bajo demanda.

3. Casos prácticos de gestión de bloqueos de aplicaciones financieras

1. Problema de puntero salvaje causado por subprocesos múltiples

En la aplicación financiera, la tecnología de conexión larga se utiliza para actualizar la información del índice de mercado. La siguiente leyenda:

inserte la descripción de la imagen aquí
He estado prestando atención a la escala de grises de la función y no se han encontrado bloqueos durante el período, sin embargo, con la apertura de nuevas versiones y el aumento continuo en la cantidad de usuarios activos, la plataforma de monitoreo de rendimiento APM comenzó a encontrar ocasionalmente accidentes Desde el registro de fallas, la falla ocurrió en la biblioteca de código abierto MQTTClient. A través de la comunicación, se descubrió que otros departamentos comerciales usaban la biblioteca de código abierto MQTTClient para tener el mismo problema.

inserte la descripción de la imagen aquí
Debido al bloqueo en línea, en la plataforma de monitoreo de rendimiento de APM, la evaluación de riesgos a través del número de bloqueos y la información de la pila determinó que se trata de un problema esporádico no esencial, por lo que el equipo de I+D comenzó a localizar el problema y encontrar la causa. A través del seguimiento continuo de bloqueos en la plataforma de monitoreo de rendimiento de APM, después de recopilar muestras de datos, podemos analizar las operaciones comunes de los usuarios antes de que la aplicación se bloquee, antes y después del cambio de aplicación. Esta es información de ruta de operación muy sospechosa.

inserte la descripción de la imagen aquí
Los enlaces largos en la aplicación Jingdong Finance utilizan el protocolo MQTT de código abierto. El equipo de I + D preguntó sobre problemas relacionados y soluciones en la comunidad de código abierto del proyecto. Aunque hay problemas similares en la comunidad, porque la biblioteca no se ha actualizado ni mantenido durante más de 2 años, no se puede encontrar ninguna solución. .

Por lo tanto, volvimos nuestra atención a los escenarios de uso comercial y al código fuente de MQTTClient. La función que causó el bloqueo en el código fuente es la siguiente, aquí está el tiempo de devolución de llamada del objeto de flujo NSStream que envía el mensaje por MQTT.

​- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode

En escenarios comerciales reales, MQTTClient funciona conectándose cuando se ejecuta en primer plano y desconectándose activamente cuando se retira al segundo plano. Entonces, el equipo de I + D quería reproducir este problema cambiando entre el anverso y el reverso, y simuló que la aplicación ingresaba a las escenas frontal y posterior a través de la simulación de código de alta frecuencia, y finalmente reprodujo este problema en modo de depuración.

inserte la descripción de la imagen aquí
El bloqueo ocurrió en el subproceso interno de MQTT. La aplicación financiera se desconectó al cambiar el fondo. El objeto MQTTCFSocketEncoder se liberó en el subproceso externo (el subproceso donde se creó el objeto). La liberación del objeto MQTTCFSocketEncoder y el flujo de procesamiento actual la cola no eran coherentes y el subproceso actual no se pudo sincronizar. Si sigue accediendo a esta dirección, el "puntero salvaje" se bloquea.

Solución de reparación: al "reservar" el objeto propio, el recuento de referencias de la memoria de montón a la que pertenece el objeto aumenta para evitar que el sistema reclame la memoria de montón durante la ejecución de la función de devolución de llamada. Luego de eso, la escena fue simulada nuevamente a través de llamadas de alta frecuencia, y no fue reproducida. El problema queda así resuelto.

- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode { 
  MQTTCFSocketDecoder *strongDecoder = self; 
  (void)strongDecoder; 
  //其他代码。。。
}

Después de resolver el problema, la aplicación financiera y otros equipos comerciales se actualizarán de manera uniforme dentro del equipo a través de actualizaciones administradas. También presentó un PR en la comunidad. En el futuro, durante el uso de conexiones largas, el equipo de I+D seguirá prestando atención a los problemas descubiertos.

Otro ejemplo obvio de punteros salvajes en desarrollo es el centro de notificaciones NSNotificationCenter. Al registrar una notificación, el centro de notificaciones guardará la dirección de memoria del objeto receptor, pero no agregará 1 al recuento de referencia del objeto receptor (unsafe_unretained). Cuando se libera el objeto, es posible que se haya reutilizado la dirección de memoria original. Cuando el centro de notificaciones envía una notificación, el mensaje aún se enviará a la dirección de memoria guardada, pero la dirección de memoria guardada ya no es el objeto original, el mensaje recibido no se puede procesar y se produce un error de bloqueo en el programa.

A menudo se producen bloqueos causados ​​por punteros salvajes, y Apple ha optimizado el uso del centro de notificaciones en iOS9. Las versiones posteriores a iOS9 eliminarán automáticamente todas las notificaciones cuando se libere el objeto. La premisa es que el objeto puede ser liberado normalmente. De la misma manera, el delegado y el origen de datos en la vista de pestañas se modificaron con unsafe_unretained antes de iOS9 y se modificaron para usar débil para modificar el puntero anti-salvaje en iOS9 y posteriores (el puntero modificado con débil se establecerá automáticamente en cero después de que el objeto en lanzamiento). Si la versión mínima admitida por la aplicación es 8.0, debe prestar especial atención al problema de los punteros salvajes en el delegado.

2. Liberación excesiva causada por recursos compartidos de subprocesos múltiples

Otro error encontrado en el desarrollo también es causado por subprocesos múltiples, que es un error de asignación de método de conjunto relativamente raro. La aplicación utiliza el marco de lotería de código abierto en lugar de imágenes GIF para hacer animaciones de atmósfera complejas para reducir el consumo de memoria. Al obtener el archivo de lotería local, debido a que es una operación que requiere mucho tiempo, se crea un subproceso para obtenerlo y descomprimirlo localmente, y volver al subproceso principal para renderizarlo una vez finalizado. Además, cuando se inicie la aplicación, enviará una solicitud de red para extraer el archivo de lotería más reciente. Si la solicitud de red es fluida y rápida, la interfaz mostrará primero el archivo de lotería más reciente.

Después de verificar el registro de estadísticas de fallas y el código comercial, pronto se sospechó que el error fue causado por subprocesos múltiples. La lectura del archivo de lotería local es el subproceso 1 y la solicitud de red es el subproceso 7. Después de obtener la ruta del archivo, tanto el subproceso 1 como el subproceso 7 llamarán a la función handleCacheFilePath para obtener el archivo de lotería. El código de la función handleCacheFilePath es el siguiente:

-(void)handleCacheFilePath:(NSString *)filePath {
​
    if (!filePath) {
        return;;
    }
​
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
​
        NSData *zipData = [NSData dataWithContentsOfFile:filePath];
​
        /*省略zip解压等其他操作 ... */
​
        manager.lottieData = zipData;
​
        dispatch_async(dispatch_get_main_queue(), ^{
            //回主线程
        });
    });
}

Después de la descompresión, asigne los datos json de lottie a self.lottieData y espere a que se muestre la animación de lottie en la siguiente interfaz. Durante la operación real, pueden ocurrir fallas en algunos casos. Debido a que el negocio de la aplicación es complejo y los escenarios de activación son extremadamente duros, los módulos de visualización y adquisición de lotería se trasladan al nuevo Domo para su reproducción.

El contenido del registro de fallas después del análisis es el siguiente:
inserte la descripción de la imagen aquí
Una mirada al tipo de falla EXC_BAD_ACCESS (SIGSEGV), muestra un error de memoria. Los errores de subprocesos múltiples de la aplicación generalmente provocan algunos problemas de memoria, los registros de fallas son muy similares a los errores de memoria. El tipo de error suele ser EXC_BAD_ACCESS (SIGSEGV). A través del análisis, el bloqueo ocurrió en la asignación de manager.lottieData = zipData. Continúe mirando la información del bloqueo, que muestra que la causa del bloqueo es la liberación excesiva de variables.

inserte la descripción de la imagen aquí
Sabemos que asignar un valor a una variable en Object-C es llamar al método setter, entonces, ¿por qué se libera en exceso? Veamos cómo se maneja el código fuente subyacente de OC (enlace del código fuente: https://opensource. apple.com/source/objc4/objc4-723/runtime/objc-accessors.mm.auto.html). A través del código ensamblador, se encuentra que el método set llama a la función objc_setProperty_nonatomic del tiempo de ejecución de OC, y parte del código fuente del tiempo de ejecución de Apple es de código abierto, así que verifique el código fuente directamente.

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

En realidad, se llama a la función reallySetProperty en objc_setProperty_nonatomic.

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }
​
    id oldValue;
    id *slot = (id*) ((char*)self + offset);//计算偏移量获取指针地址
​
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);//retain新值newValue
    }
​
    if (!atomic) {//非原子属性
        oldValue = *slot;//第一步
        *slot = newValue;//第二步
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
​
    objc_release(oldValue);//释放旧值 引用计数-1
}

La clave del problema se encuentra en las líneas 20 a 31 de la función realmente EstablecerPropiedad.Si se trata de una operación no atómica, asigne directamente la ranura al objeto oldValue, luego pague el nuevo valor a la ranura y, finalmente, disminuya el recuento de referencia de liberación de oldValue en uno. Si se trata de una operación atómica (modificación atómica), se agregará un spinlock antes de que se lea la variable.Después de que el subproceso actual adquiera el spinlock spinlock, otro subproceso no puede adquirir el spinlock y solo puede esperar en su lugar.

Dado que lottieData es una variable compartida y está decorada con nonatomic nonatomic, la asignación del subproceso 1 entrará en la condición no atómica, suponiendo que cuando el subproceso 1 termine de ejecutar la asignación de oldValue = *slot, el intervalo de tiempo se agota. En este momento, la programación de la CPU comienza a ejecutar el subproceso n.° 7, y el subproceso n.° 7 también realizará la misma operación para asignar un valor a oldValue. Una vez completada la asignación, ejecute el comando objc_release(oldValue) para liberar el espacio de memoria señalado por oldValue. En este momento, se ha liberado el espacio de memoria señalado por oldValue. Después de que se ejecuta el subproceso n.° 7, la CPU gira para ejecutar el subproceso n.° 1. Cuando el subproceso n.° 1 ejecuta de nuevo el método objc_release(oldValue), fallará. El motivo es realizar una operación de liberación en una memoria que ya ha sido liberada. Esto también corresponde al error overrelease_error en la pila de fallas.

Solución: después de una comprensión profunda del principio, la solución al problema será una cuestión de rutina. lottieData es una variable compartida mantenida por el administrador, que se puede cambiar para usar una decoración atómica para evitar la competencia de subprocesos múltiples. Debido a que los métodos set y get de variables modificados por atomic agregarán un bloqueo de giro, si se trata de una escena con lectura frecuente, el bloqueo de giro consumirá más recursos de CPU. Afortunadamente, no hay muchos escenarios de uso de lottieData combinados con análisis de negocios, y no causará un desperdicio excesivo de recursos de CPU. Además, puede modificar la lógica del código para declarar lottieData como una variable temporal y usar la variable temporal como el valor de retorno de la función para resolver el problema de la competencia de subprocesos múltiples.

3. Excepción de llamada de método

Para la variedad caleidoscópica de bloqueos, las excepciones de llamadas a métodos son uno de los problemas más fáciles de solucionar. En la aplicación, algunos métodos no están implementados debido a negligencia, y algunos métodos de objetos no existen debido a la liberación de memoria. No discutiremos la solución, porque los desarrolladores ya la han conocido y resuelto muchas veces. Aquí exploramos principalmente el proceso de generación de excepciones de "selector no reconocido enviado a la instancia".

Todos sabemos que la esencia de llamar al método de un objeto en OC es enviar un mensaje al objeto. Las llamadas a métodos se traducen en funciones objc_msgSend durante la compilación. El primer parámetro requerido es el receptor del mensaje, el segundo parámetro requerido es el nombre del método, seguido de los parámetros pasados.
objc_msgSend(id propio, SEL op, … )

Estos son los pasos para enviar un mensaje:

(1) Detectar si es necesario ignorar el selector. Si existe un mecanismo de recolección de elementos no utilizados en un sistema Mac OX, se ignorará la función de retención/liberación.
(2) Compruebe si el objeto de respuesta es nulo. El sistema de tiempo de ejecución ignorará el envío de un mensaje a un objeto nulo.
(3) Desde la caché, se realiza mediante el método de búsqueda de puntero de función IMP, y si existe, se ejecuta el método.
(4) Si no se puede encontrar en el caché, buscará en la lista de métodos de Clase y buscará recursivamente en la lista de métodos de la clase principal.
(5) Si no encuentra ninguno, ingrese el método dinámico de resolución y el proceso de reenvío de mensajes.

inserte la descripción de la imagen aquí
Envía un mensaje a un objeto a través de la función objc_msgSend. Si el objeto no se puede procesar después de varios procesos, se lanzará una excepción. Antes de fallar, el sistema de tiempo de ejecución de OC pasará por los siguientes dos pasos:

  • DynamicMethod Resolution (Resolución de método dinámico)
    toma un método de objeto como ejemplo. El sistema llamará a resolveInstanceMethod: para agregar dinámicamente un método para el objeto. Si el valor de retorno es sí, buscará el método de instancia nuevamente. Si el valor de retorno es No, entrará en el proceso de reenvío de mensajes.
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(handleOpenPage:)) {
        IMP imp = class_getMethodImplementation([self class], @selector(openNewPage:));
        class_addMethod([self class], sel, imp, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

El ejemplo anterior agrega dinámicamente la implementación (openNewPage:) al método handleOpenPage: del objeto de instancia. Donde "v@:" representa el valor devuelto y los parámetros. El significado de cada carácter se puede ver en Codificaciones de tipos. (Escriba el enlace Codificaciones: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)

  • Reenvío de mensajes (Message Forwarding)
    El reenvío de mensajes llamará al método forwardingTargetForSelector para obtener un nuevo objetivo como receptor y volver a ejecutar el selector. Si es un método de objeto, debe anularse: (id)forwardingTargetForSelector:(SEL)aSelector method. Si es un método de clase, invalide el método + (id)forwardingTargetForSelector:(SEL)aSelector.
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(handleOpenPage:)){
        return _otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

Si el objeto devuelto no es válido (nulo o el mismo que el antiguo receptor), ingrese el proceso de reenvío de invocación. Este método se puede anular en el programa para definir la lógica de reenvío. El parámetro anInvocation es el objeto generado cuando el sistema de tiempo de ejecución llama al método methodSignatureForSelector: para obtener la firma del método. Al volver a escribir forwardInvocation:, también debe volver a escribir el método methodSignatureForSelector:, de lo contrario, se generará una excepción.

- (void)forwardInvocation:(NSInvocation *)anInvocation {
​
    if ([_otherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:_otherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }

El proceso completo de reenvío de mensajes se muestra en la figura:

inserte la descripción de la imagen aquí
Cuando un objeto no implementa el método correspondiente, el sistema de tiempo de ejecución notificará al objeto a través del mensaje de invocación de reenvío. Cada objeto hereda el método forwardInvocation: de la clase NSObject, y la implementación del método en NSObject simplemente llama a doesNotRecognizeSelector:. Al implementar nuestro propio método forwardInvocation:, podemos reenviar mensajes a otros objetos en la implementación del método. Si no se maneja en estos procesos, se generará una excepción y se producirá un bloqueo.

Los anteriores son casos típicos, código fuente y análisis principal de aplicaciones financieras en el proceso de gestión de fallas. En el proceso de gestión de casos anterior, el equipo de I+D ha acumulado una experiencia muy útil en la detección de problemas.
4. Precipitación de la experiencia de combate real

1. La ruta de operación del usuario es la mejor indicación para reproducir el problema. La información de la pila en el registro de fallas puede rastrear qué páginas ha ingresado el usuario, en qué estado se encuentra, si se está ejecutando en primer plano o en segundo plano. Combinado con la plataforma de monitoreo de rendimiento de APM, el estado actual de la aplicación se puede analizar con mayor claridad. De acuerdo con la identificación de inicio, puede ver el estado de la red de la aplicación antes del bloqueo y qué solicitudes de red se enviaron. Estos pueden ayudar a los desarrolladores a reproducir problemas más rápido.

2. El bloqueo se produce en el código comercial de la propia aplicación, que a menudo es fácil de resolver. Si se produce en la biblioteca de código abierto de terceros utilizada, primero puede ir a la comunidad de código abierto para encontrar el mismo problema. Algunos Las bibliotecas de código abierto mantenidas con frecuencia han pasado por muchos escenarios de aplicaciones reales Prueba, habrá problemas y soluciones similares. Si el equipo de I+D lo resuelve, también puede enviar una solicitud de extracción relacionada en la comunidad de código abierto. Y comparte la experiencia de resolverlo con desarrolladores que se encuentren con el mismo problema.

3. Cualquier falla tiene condiciones específicas para que ocurra. Cuando es realmente imposible de reproducir, debe encontrar una solución desde otro ángulo. Leer el código fuente es una muy, muy buena manera de comprender la naturaleza del problema. El sistema de Apple es una ecología de circuito cerrado, pero algunos El código fuente es de código abierto, puede leerlo en línea o descargar el código fuente relevante para una comprensión profunda.

4. El registro de fallas es la información de primera mano para resolver el problema de fallas. El registro de fallas contiene la información de la pila y la causa de la falla cuando la aplicación falla. La lectura de registros de fallas es muy importante en el desarrollo. Por lo tanto, es necesario introducir brevemente el contenido del registro de fallas en la siguiente sección.

Cinco, análisis de registro de fallas

1. Contenido del registro de fallas

Después de que ocurre un bloqueo, lo primero que pensamos es en qué línea de código se encuentra el bloqueo, cuál es la pila y qué subprocesos se están ejecutando, todo lo cual se incluye en el informe de bloqueo. Tomando la demostración en WWDC como ejemplo, ChocolateChip se ejecuta en el emulador. La parte superior del registro de fallas contiene información resumida, incluido el nombre de la aplicación, el número de versión, el sistema operativo y la fecha y hora de la falla.

inserte la descripción de la imagen aquí
La siguiente parte es la causa del bloqueo, el error ocurre en el hilo principal, el tipo de bloqueo es SIGILL, es decir, la CPU está ejecutando una instrucción inexistente o no válida. El motivo específico del bloqueo que se muestra en Fatal error es forzar el desempaquetado de una variable con un valor opcional de cero.

inserte la descripción de la imagen aquí
La siguiente parte es la información de la pila del bloqueo, puede verificar el hilo bloqueado actual, la información de la pila en el momento del bloqueo, etc.

La información de pila original del bloqueo se muestra en la siguiente figura:

inserte la descripción de la imagen aquí
La información de la pila original del bloqueo no es conveniente para ubicar directamente el problema del bloqueo, y la información de la pila original debe simbolizarse (). El proceso de convertir direcciones de memoria en nombres de métodos, nombres de archivos y números de línea se denomina simbolización. Hay 3 elementos necesarios para la simbolización del registro de fallas.

(1) Los registros de fallas se pueden obtener desde el panel de fallas al abrir la ventana Organizador en la opción Ventana de Xcode, o se pueden descargar desde el fondo del envío de la aplicación. Las aplicaciones con capacidades de monitoreo completas pueden recopilar e informar al servidor para su almacenamiento a través de la aplicación y luego descargar registros de fallas del servidor.

(2) Tabla de símbolos. dSYM (SÍMBOLOS de depuración) también se denomina tabla de símbolos de depuración. Cada aplicación compilada y cargada a través de Xcode se archivará automáticamente. Abra la ventana Organizador en la opción Ventana de Xcode, y los archivos de la aplicación compilados se mostrarán en el menú Archivos. Seleccione el archivo para ver el archivo dsym de la aplicación a través de mostrar en el buscador → mostrar el contenido del paquete.

(3) La ruta /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash puede obtener la herramienta simbólica que viene con Xcode.

Copie los tres archivos anteriores en el mismo archivo, verifique que los UUID de los tres archivos sean consistentes, luego use la terminal para ingresar al directorio actual y ejecute el comando simbólico ./symbolicatecrash-vxxxx.crash xxxx.app.dSYM. Abra el archivo xxxx.crash después de completarlo, puede ver la pila simbolizada y puede ver claramente el nombre del método, el nombre del archivo y el número de línea y otra información. La información simbolizada se muestra en la siguiente figura:

inserte la descripción de la imagen aquí
Por supuesto, habrá información de bajo nivel en la parte inferior del registro, incluido el estado de registro del subproceso bloqueado y la imagen de datos binarios cargada en el proceso, que son los datos del archivo ejecutable de la aplicación. Xcode busca símbolos, archivos e información de números de línea a través de la simbolización y los muestra en la pila.

Información de registro:
inserte la descripción de la imagen aquí
Imagen de archivo ejecutable:
inserte la descripción de la imagen aquí
lo anterior es todo el contenido de un registro de fallas, y se debe prestar atención a la siguiente información útil en estos contenidos.

Primero, comience con el tipo de falla. En el ejemplo, el tipo de excepción es EXC_BAD_INSTRUCTION y la CPU está ejecutando una instrucción ilegal. El mensaje de bloqueo indica que la causa del bloqueo fue el desempaquetado forzado de un objeto opcional.

En segundo lugar, el bloqueo está en el subproceso principal y la pila contiene la pila de funciones que se estaba ejecutando en el momento del bloqueo. En la pila se ve la función fatalErrorMessage, esta es una función del sistema, y ​​una función en el código la llama.

Como puede ver en el seguimiento de la pila (RecipeImage.swift:26), la llamada se produce en la línea 26 del archivo RecipeImage.swift. En el código hay una clase de Receta cuya función de imagen es llamada y esa función llama a la función fatalErrorMessage debido a algún error. Al obtener la imagen, el código desempaqueta la ruta opcional a la fuerza, lo que provoca un bloqueo. Vea abajo:

inserte la descripción de la imagen aquí

2. Dónde ver el registro de fallas

Después de interpretar el contenido del registro de fallas, ¿dónde puedo ver el registro de fallas?

(1) Utilice AppleID para iniciar sesión en Xcode y ver el elemento de bloqueo en el organizador en la barra de menú.
(2) Si puede obtener la máquina bloqueada, puede obtener directamente la información de registro en el dispositivo y filtrar la información de registro relacionada con la aplicación.
(3) Plataforma de monitoreo de aplicaciones, que recopila información sobre fallas a través del lado de la aplicación y la clasifica y analiza en segundo plano. Es más conveniente ayudar a desarrollar y localizar problemas.

6. Estrategia de gobierno especial de Crash

1. Configure un proyecto intensivo especial y establezca un objetivo por etapas

Antes de que fallara la gobernanza especial de JD Financial App, la tasa de fallas de la aplicación era inestable y fluctuaba con los lanzamientos de versiones y las iteraciones comerciales. Hay muchos tipos de problemas de bloqueo en las estadísticas en línea. Hay fallas simples fuera de los límites de la matriz, fallas al insertar valores nulos y problemas de memoria que causan punteros salvajes y excepciones de subprocesos múltiples. En respuesta a estas situaciones, el equipo creó un equipo especial para la gestión de accidentes, ordenados según el número de accidentes y la urgencia, y resolvió cíclicamente los diez problemas principales de la lista de accidentes.

2. Ubicación y distribución del módulo Crash

La aplicación actual ya no se limita a un determinado negocio, sino que ya es una colección de múltiples funciones comerciales. Una vez que toda la aplicación se divide en componentes, los códigos de cada función comercial se integran en la aplicación en forma de .a, .framwork, etc. Cuando se produce un bloqueo, es difícil encontrar en qué módulo empresarial se encuentra el código de bloqueo, lo que provoca grandes dificultades en la distribución y resolución del bloqueo. En función de este problema, el grupo de trabajo de bloqueo realiza la coincidencia de nombres de archivo a través de linkmap, o usa el comando grep para encontrar archivos .a y .framework que contienen códigos de bloqueo en el binario. Si la búsqueda tiene éxito, se enviará a todas las partes comerciales de manera conveniente para su procesamiento oportuno.

3. Establecer un sistema de monitoreo de aplicaciones

En el proceso anterior de resolución de fallas, I+D tomó la iniciativa de ir al fondo del desarrollador de Apple o al fondo de monitoreo de fallas de terceros (bugly, Youmeng, etc.) para verificar la tendencia actual de fallas, etc. Este método requiere que I+D sea autodidacta. conducido y tiene un retraso , Por ejemplo, es difícil responder de manera oportuna a una gran cantidad de bloqueos que ocurren repentinamente al cambiar configuraciones en segundo plano. La aplicación financiera monitorea la tendencia de los bloqueos a través del sistema de monitoreo de desempeño de APM, establece el umbral de bloqueo y activa automáticamente una alarma dentro de un número específico de bloqueos dentro de un cierto período de tiempo, y activa una alarma si la tasa de bloqueo supera el umbral para un período de tiempo determinado, y envía correos electrónicos, herramientas de comunicación interna y otros medios para notificar al responsable para que lo atienda en tiempo y forma. Al mismo tiempo, se envían automáticamente informes de rendimiento semanales para evaluar el rendimiento del sistema.

4. Continuar prestando atención a los problemas existentes y frenar los problemas emergentes

Debe haber algunos problemas en el desarrollo diario que son persistentes y difíciles de reproducir. Este tipo de problema tiene una pequeña cantidad de bloqueos cuando la actividad diaria es estable, pero continúa ejecutándose a través de múltiples versiones. Puede estar sumergido en otros bloqueos en momentos normales, pero cuando la actividad diaria como 618 y Double Eleven aumenta considerablemente. , el número de accidentes aumentará. Este es también un período especial para descubrir problemas. Además, mientras resuelve los problemas originales, concéntrese en monitorear el nuevo negocio después de que esté en línea. En caso de problemas que no aparecen en escala de grises, se bloqueará y explotará cuando más usuarios lo utilicen.

5. Especificación de codificación

Las buenas prácticas de codificación ayudan a reducir los errores de codificación. Los complejos algoritmos y la lógica del programa son solo una pequeña parte del programa, y ​​la mayoría de los bloqueos se pueden evitar mediante la revisión del código. Las matrices de diccionarios convencionales insertan valores nulos, no se pueden encontrar métodos, etc. Básicamente se han eliminado después del desarrollo, las pruebas y la escala de grises.

7. Resumen

Este artículo se centra en el conocimiento básico de los bloqueos, incluida la ocurrencia de bloqueos, escenarios típicos de bloqueos y cómo interpretar los registros de bloqueos. Al mismo tiempo, combinado con los ejemplos reales de fallas que se encuentran en el desarrollo de aplicaciones financieras, se analizará en detalle desde la causa del problema, cómo ubicar la ubicación de la falla, cómo reproducirla y cómo repararla. Durante el desarrollo, puede ubicar el problema de acuerdo con el tipo de falla y el registro de fallas, y brindar una solución para las fallas típicas. El rendimiento de la aplicación y la experiencia del usuario son un proceso de optimización a largo plazo. Los bloqueos no se detendrán con la optimización. Solo la atención y la optimización continuas pueden hacer que las aplicaciones actuales que se han disparado en volumen de código avancen de manera constante.

En la sexta parte de este artículo, se menciona el sistema de monitoreo de desempeño de APM. El sistema de monitoreo de desempeño de APM es una plataforma de monitoreo de desempeño construida por el equipo móvil de JD Technology y el equipo de operación y mantenimiento. Toma tiempo para comenzar, fluctúa en las solicitudes de red, y lleva tiempo abrir webView. , El seguimiento del usuario, el monitoreo de la página nativa, la congelación de fallas, el monitoreo personalizado y otras funciones están disponibles, logrando un monitoreo de enlace completo desde el inicio hasta la conexión del servidor para salir. En la actualidad, varias aplicaciones en JD Technology se han conectado al sistema de monitoreo de rendimiento de APM, que brinda una mayor garantía de calidad para cada aplicación y equipo comercial.

Autor de este artículo: Jingdong Technology Wu Xinyu
Para conocer más prácticas e innovaciones técnicas, preste atención a la cuenta oficial de WeChat de "Jingdong Technology Technology Talk"

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/JDDTechTalk/article/details/119238048
Recomendado
Clasificación