Prática de governança de falha do aplicativo JD Financial

I. Introdução

No início de 2020, a escala de usuários do JD Financial App superou em muito a de alguns anos atrás, e a atividade diária também dobrou. Ao mesmo tempo, também notamos que a taxa de falhas do aplicativo aumentou ligeiramente com a iteração da versão. Quando percebemos que o crash do App havia prejudicado a experiência diária do usuário, a taxa de crash chegou a alguns por mil. A taxa de travamento é um indicador importante para medir a qualidade do App, que não só afeta a estabilidade do App, mas também afeta diretamente a experiência do usuário e o crescimento do negócio. Se ocorrer uma falha durante a inicialização, o aplicativo poderá ser desinstalado diretamente, o que causará ainda mais propaganda boca a boca e queda no valor da marca. Portanto, embora o aplicativo financeiro esteja se desenvolvendo rapidamente, ele também presta mais atenção à construção de qualidade.

A taxa crescente de falhas do JD Financial App é inseparável do rápido desenvolvimento dos negócios de aplicativos. Cenários de negócios cada vez mais complexos, acoplamento lógico entre vários negócios e a expansão das funções do aplicativo tornam os programas mais propensos a erros. Alguns códigos antigos foram afetados lentamente após várias iterações de negócios, e os erros em alguns cenários especiais demoram muito e só aparecem quando um grande número de usuários os usa, o que torna o reparo do erro menos oportuno. O problema de travamento que aparecia na escala de cinza tornou-se um estado de espera a ser observado após a falha da pesquisa e tornou-se proeminente quando o número de usuários aumentou significativamente após o acesso à Internet. O lento acúmulo de travamentos fez com que a taxa de travamento se tornasse um número muito gritante em uma determinada versão. Com base nessa situação, a equipe decidiu internamente administrar minuciosamente a situação naquele momento e encontrar uma maneira de mantê-la.

O gerenciamento de falhas do JD Financial App durou várias versões, e as 20 principais falhas foram basicamente reparadas. No entanto, o reparo do acidente não é fácil, alguns problemas difíceis de reproduzir serão resolvidos após o reparo, observação e reparo. Durante o período de reparo dos problemas originais, o negócio de aplicativos também é atualizado continuamente e traz alguns novos problemas. A equipe de P&D presta atenção especial aos problemas emergentes e usa o estágio de lançamento em escala de cinza para eliminar os problemas pela raiz. No final, o aplicativo financeiro estabilizou a taxa de falhas abaixo de uma em 10.000.

De acordo com o relatório de experiência de desempenho da indústria móvel de 2020, a taxa média de falhas da indústria de aplicativos é de 0,29%, a taxa média de falhas da indústria do lado do Android é de 0,32% e a taxa média de falhas da indústria de aplicativos do lado do iOS é de 0,10 %.

insira a descrição da imagem aqui
O aplicativo JD Finance passou por reparos contínuos de alta qualidade e a taxa de falhas é duas ordens de magnitude menor que a média do setor e permaneceu estável em 0,007% por muito tempo.

Os dados da taxa de travamento do usuário vêm do sistema de monitoramento de desempenho do APM.
insira a descrição da imagem aqui
A taxa de falhas do JD Financial App é muito superior ao nível da indústria, o que é inseparável da exploração técnica aprofundada da equipe de P & D. O conhecimento básico de falhas é um pré-requisito para a exploração técnica. Este artigo explicará o conhecimento básico de falhas de raso a profundo e compartilhará o processo de solução de casos típicos de falha.

2. Definição de acidente

1. O motivo do acidente

Uma falha é uma resposta explícita da CPU a uma exceção, e o tratamento de exceção da CPU é baseado em interrupções. Interrupção significa que a UCP suspende o programa que está sendo executado, salva a cena e então executa o programa de processamento correspondente.Após o processamento do evento, ela retorna ao breakpoint e continua executando o programa "interrompido".

Introduzido nas informações relevantes do sistema operacional: interrupção (interrupção) e exceção (exceção) têm significados diferentes em diferentes arquiteturas de CPU.

  • Por exemplo, na arquitetura Intel, a entrada de processamento de interrupção é definida pela tabela de despacho de interrupção (IDT) no kernel do sistema operacional. Existem 255 vetores de interrupção no IDT, dos quais os primeiros 20 são definidos como entradas de processamento de exceção. ou seja, a interrupção contém uma exceção.
  • Na arquitetura ARM, a entrada do processamento da interrupção está no vetor de exceção (vetor de exceção), e 3 dos 8 vetores de exceção estão relacionados a interrupções, ou seja, as exceções incluem interrupções.

Independentemente de como definir interrupções e exceções, quando uma exceção ocorre na CPU, ela irá transferir o controle do programa antes da exceção para o manipulador de exceção, e a CPU não obterá direitos de execução inferiores. manipulador de exceção correspondente. O ciclo de vida de uma instrução em um pipeline clássico de cinco estágios da CPU é [buscar, decodificar, executar, acessar a memória, escrever novamente], e exceções da CPU podem ocorrer em cada estágio, como na arquitetura ARM:

  • Exceção "Data abort" gerada na fase "execution": Se o endereço da instrução de acesso a dados do processador não existe, ou o endereço não permite o acesso da instrução atual, uma exceção de abortamento de dados é gerada.
  • Anormalidade "Prefetch abort" gerada na fase "instruction fetch": Se o endereço da instrução pré-buscada do processador não existir, ou o endereço não permitir o acesso da instrução atual, a memória enviará um sinal de abortar ao processador, mas quando a instrução pré-buscada é executada, uma exceção de abortamento de pré-busca de instrução é gerada.

Os manipuladores correspondentes às duas exceções chamarão direta ou indiretamente a função exception_triage() do kernel Mach e passarão EXC_BAD_ACCESS como um parâmetro de entrada, e exception_triage() usará o mecanismo de passagem de mensagem Mach para entregar a exceção.

2. Como ocorre a falha no sistema iOS

No kernel do sistema iOS (Mach), as exceções são tratadas por meio da configuração básica "mecanismo de passagem de mensagem" no kernel. A exceção não é mais complicada do que uma mensagem. A exceção é lançada pelo thread e tarefa errados por meio de msg_send(), e então manipulado por um O programa captura via msg_recv(). Um manipulador pode manipular a exceção, pode limpar a exceção e pode decidir encerrar o aplicativo.

Para o App, quando o App tenta fazer algo que não é permitido, como a CPU não pode executar determinados códigos (acessar memória inválida, modificar a área de armazenamento somente leitura etc.) alto uso de memória, o tempo de inicialização do aplicativo é muito longo etc.), o sistema operacional protegerá a experiência do usuário encerrando seu aplicativo.

Em algumas linguagens de desenvolvimento, alguns objetos de programação interrompem a execução do programa e travam quando encontram erros. Por exemplo, acessar um array fora dos limites em Object-C/Swift, NSArray/Array irá desencadear uma falha e interromper a execução do programa.

2. Vários tipos de falhas comuns

1. Ponteiro selvagem

Um ponteiro selvagem aponta para um endereço de memória incerto e várias situações incertas podem ocorrer ao acessar esse endereço de memória por meio de um ponteiro selvagem. Se este endereço de memória não estiver coberto, não haverá necessariamente um problema.Se estiver coberto ou alocado como um espaço inacessível, o programa travará diretamente. Se for julgado que a falha é causada por ponteiros selvagens, o código de falha atual provavelmente não é a causa da falha, e a causa real da falha precisa ser encontrada analisando o relacionamento de chamada.

Na linguagem C, os ponteiros selvagens geralmente ocorrem quando o valor inicial (endereço aleatório) não é atribuído depois que a variável é declarada ou o ponteiro não é esvaziado após o lançamento. Em Object-C, os ponteiros selvagens geralmente ocorrem em vários encadeamentos e a variável acessada pelo thread atual é acessado por outro thread. O valor padrão do ponteiro em Object-C é nil, que é o mesmo que NULL na linguagem C, o que significa que o ponteiro não aponta para nenhum espaço de memória. Os resultados de erros de ponteiro selvagens geralmente são variáveis ​​ou exceções de acesso à memória, e o tipo de travamento comum são erros de memória EXC_BAD_ACCESS.

2. Impasse

No sistema iOS, usar dispatch_sync para executar tarefas de sincronização no thread principal causará um impasse. Se a tarefa for executada no thread principal (exemplo de código abaixo) devido a alguns cenários complexos de lógica de negócios, isso fará com que o aplicativo trave.

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

O programa ficará preso na primeira linha do corpo da função e a mensagem de erro é a seguinte: Thread 1: EXC_BAD_INSTRUCTION (código=EXC_I386_INVOP, subcódigo=0x0)

O código de exemplo acima é o impasse mais típico do thread principal. Uma tarefa de sincronização "NSLog(@"Sync Task Result")" é adicionada à fila principal, de modo que o thread principal suspenderá o código atual para executar o bloco de código do bloco e aguarde a função dispatch_sync Continue a execução após o retorno. Mas a fila principal (fila principal) é uma fila serial seguindo o princípio do primeiro a entrar, primeiro a sair. Atualmente, o thread principal está executando a função SceneAnalysis e dispatch_sync precisa aguardar a conclusão da função SceneAnalysis. SceneAnalysis e dispatch_sync esperam um pelo outro, o que causa um impasse.

3, cão de guarda

Se o aplicativo demorar muito para executar uma determinada tarefa (iniciar, encerrar ou responder a eventos do sistema), o sistema operacional encerrará o processo atual. O sinal mais óbvio de uma falha acionada pelo mecanismo de vigilância é o código de erro: 0x8badf00d. Normalmente, o log de travamento ficará assim:

insira a descrição da imagem aqui
Quando o tempo de inicialização do aplicativo exceder o valor máximo permitido (geralmente 20s), o mecanismo de vigilância será acionado no sistema iOS para encerrar o processo imediatamente. Vale ressaltar que o travamento acionado pelo watchdog não será coletado no monitoramento de erros desenvolvido por ele mesmo, podendo o log do travamento ser obtido no dispositivo de travamento. A Apple desliga o mecanismo de vigilância ao usar o simulador, e o mecanismo de vigilância não será acionado no modo de depuração. Portanto, no processo de desenvolvimento, o processo de inicialização do App deve ser conciso e carregado sob demanda.

3. Casos práticos de gerenciamento de falhas de aplicativos financeiros

1. Problema de ponteiro selvagem causado por multithreading

No APP financeiro, a tecnologia de conexão longa é utilizada para atualizar as informações do índice de mercado. A seguinte legenda:

insira a descrição da imagem aqui
Venho prestando atenção na escala de cinza da função, e não foram encontrados travamentos no período, porém, com a abertura de novas versões e o aumento contínuo do número de usuários ativos, a plataforma de monitoramento de performance APM começou a encontrar eventuais falhas. No log de travamento, o travamento ocorreu na biblioteca de software livre MQTTClient. Por meio da comunicação, descobriu-se que outros departamentos de negócios usavam a biblioteca de software livre MQTTClient para ter o mesmo problema.

insira a descrição da imagem aqui
Devido ao crash online, na plataforma de monitorização de performance APM, a avaliação de risco através do número de crashes e informação de stack determinou que se trata de um problema esporádico não essencial, pelo que a equipa de I&D começou a localizar o problema e a encontrar a causa. Por meio do rastreamento contínuo de falhas na plataforma de monitoramento de desempenho do APM, após a coleta de amostras de dados, podemos analisar as operações comuns dos usuários antes que o aplicativo falhe - antes e depois da troca de aplicativo. Esta é uma informação de caminho de operação muito suspeita.

insira a descrição da imagem aqui
Os links longos no Jingdong Finance App usam o protocolo MQTT de código aberto. A equipe de P&D perguntou sobre questões e soluções relacionadas na comunidade de código aberto do projeto. Embora existam problemas semelhantes na comunidade, porque a biblioteca não foi atualizada e mantida para mais de 2 anos, nenhuma solução pode ser encontrada.

Portanto, voltamos nossa atenção aos cenários de uso de negócios e ao código-fonte do MQTTClient. A função que causou a falha no código-fonte é a seguinte, aqui está o tempo de retorno de chamada do objeto de fluxo NSStream enviando a mensagem por MQTT.

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

Em cenários de negócios reais, o MQTTClient funciona conectando-se ao executar em primeiro plano e desconectando-se ativamente quando recua para o segundo plano. Portanto, a equipe de P&D queria reproduzir esse problema alternando entre a frente e o verso e simulou o aplicativo entrando nas cenas frontal e traseira por meio da simulação de código de alta frequência e, finalmente, reproduziu esse problema no modo de depuração.

insira a descrição da imagem aqui
A falha ocorreu no thread interno do MQTT. O aplicativo financeiro foi desconectado ao alternar o plano de fundo. O objeto MQTTCFSocketEncoder foi liberado no thread externo (o thread onde o objeto foi criado). A liberação do objeto MQTTCFSocketEncoder e o fluxo de processamento atual fila não eram consistentes e o thread atual falhou ao sincronizar o estado, ainda assim, acessar este endereço faz com que o "ponteiro selvagem" trave.

Solução de reparo: Ao "reservar" o objeto self, a contagem de referência da memória heap à qual o objeto pertence é aumentada para evitar que a memória heap seja recuperada pelo sistema durante a execução da função de retorno de chamada. Depois disso, a cena foi simulada novamente por meio de chamadas de alta frequência e não foi reproduzida. O problema está assim resolvido.

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

Depois de resolver o problema, o aplicativo financeiro e outras equipes de negócios serão atualizados uniformemente dentro da equipe por meio de atualizações gerenciadas. Também enviou um PR na comunidade. No futuro, durante o uso de conexões longas, a equipe de P&D continuará atenta aos problemas descobertos.

Outro exemplo óbvio de ponteiros selvagens em desenvolvimento é o centro de notificação NSNotificationCenter. Ao registrar uma notificação, a central de notificações salvará o endereço de memória do objeto receptor, mas não adicionará 1 à contagem de referência do objeto receptor (unsafe_unretained). Quando o objeto é liberado, o endereço de memória original pode ter sido reutilizado. Quando o centro de notificação envia uma notificação, a mensagem ainda será enviada para o endereço de memória salvo, mas o endereço de memória salvo não é mais o objeto original, a mensagem recebida não pode ser processada e ocorre um erro de falha no programa.

Freqüentemente ocorrem falhas causadas por ponteiros selvagens, e a Apple otimizou o uso do centro de notificação no iOS9. As versões posteriores ao iOS9 removerão automaticamente todas as notificações quando o objeto for lançado. A premissa é que o objeto pode ser liberado normalmente. Da mesma forma, o delegado e o dataSource no tabview foram modificados com unsafe_unretained antes do iOS9 e modificados para usar o ponto fraco para modificar o ponteiro anti-wild no iOS9 e posterior (o ponteiro modificado com fraco será automaticamente definido como nil após o objeto é libertado). Se a versão mínima suportada pelo aplicativo for 8.0, você precisará prestar atenção extra ao problema de ponteiros selvagens no delegado.

2. Liberação excessiva causada por recursos compartilhados multiencadeados

Outro erro encontrado no desenvolvimento também é causado por multi-threading, que é um erro de atribuição de método de conjunto relativamente raro. O aplicativo usa a estrutura lottie de código aberto em vez de imagens GIF para fazer animações de atmosfera complexas para reduzir o consumo de memória. Ao obter o arquivo lottie local, por ser uma operação demorada, uma sub-thread é criada para obtê-lo e descompactá-lo localmente, e retornar ao thread principal para renderização após a conclusão. Além disso, quando o aplicativo for iniciado, ele enviará uma solicitação de rede para extrair o último arquivo lottie.Se a solicitação de rede for suave e rápida, a interface exibirá primeiro o arquivo lottie mais recente.

Depois de verificar o log de estatísticas de travamento e o código comercial, logo se suspeitou que o erro foi causado por multi-threading. A leitura do arquivo lottie local é o thread 1 e a solicitação de rede é o thread 7. Depois de obter o caminho do arquivo, o thread 1 e o thread 7 chamarão a função handleCacheFilePath para obter o arquivo lottie. O código da função handleCacheFilePath é o seguinte:

-(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(), ^{
            //回主线程
        });
    });
}

Após a descompactação, atribua os dados json de lottie a self.lottieData e aguarde a exibição da animação lottie na próxima interface. Durante a operação real, falhas podem ocorrer em alguns casos. Como o negócio do aplicativo é complexo e os cenários de acionamento são extremamente difíceis, os módulos de aquisição e exibição de lottie são movidos para o novo Domo para reprodução.

O conteúdo do log de travamento após a análise é o seguinte:
insira a descrição da imagem aqui
Uma olhada no tipo de travamento EXC_BAD_ACCESS (SIGSEGV), mostra um erro de memória. Os erros de multi-threading do aplicativo geralmente levam a alguns problemas de memória, os logs de travamento são muito semelhantes aos erros de memória. O tipo de erro geralmente é EXC_BAD_ACCESS (SIGSEGV). Através da análise, o travamento ocorreu na atribuição de manager.lottieData = zipData. Continue a observar as informações do travamento, que mostram que a causa do travamento é a liberação excessiva de variáveis.

insira a descrição da imagem aqui
Sabemos que atribuir um valor a uma variável em Object-C é chamar o método setter, então por que ele foi lançado em excesso? Vamos ver como o código-fonte subjacente do OC é tratado (link do código-fonte: https://opensource. apple.com/source/objc4/objc4-723/runtime/objc-accessors.mm.auto.html). Por meio do código assembly, descobriu-se que o método set chama a função objc_setProperty_nonatomic do tempo de execução OC e parte do código-fonte do tempo de execução da Apple é de código aberto, portanto, verifique o código-fonte diretamente.

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

A função reallySetProperty é realmente chamada em 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
}

A chave para o problema está nas linhas 20-31 da função reallySetProperty. Se for uma operação não atômica, atribua diretamente o slot ao objeto oldValue, depois pague o novo valor ao slot e, finalmente, diminua a contagem de referência de release oldValue por um. Se for uma operação atômica (modificação atômica), um spinlock será adicionado antes da variável ser lida. Após o spinlock do spinlock ser adquirido pelo thread atual, outro thread não pode adquirir o spinlock e pode apenas esperar no local.

Como lottieData é uma variável compartilhada e é decorada com nonatomic nonatomic, a atribuição do thread 1 entrará na condição não atômica, assumindo que quando o thread 1 terminar de executar a atribuição de oldValue = *slot, a fatia de tempo se esgotará. Nesse momento, o escalonamento da CPU começa a executar o thread nº 7, e o thread nº 7 também executará a mesma operação para atribuir um valor a oldValue. Após a conclusão da atribuição, execute o comando objc_release(oldValue) para liberar o espaço de memória apontado por oldValue. Neste momento, o espaço de memória apontado por oldValue foi liberado. Depois que o thread nº 7 é executado, a CPU está girando para executar o thread nº 1. Quando o thread nº 1 executar o método objc_release(oldValue) novamente, ele travará. O motivo é realizar uma operação de liberação em uma memória que já foi liberada. Isso também corresponde ao erro overrelease_error na pilha de falhas.

Solução: Após uma compreensão profunda do princípio, a solução do problema será natural. lottieData é uma variável compartilhada mantida pelo gerenciador, que pode ser alterada para usar decoração atômica para evitar a competição multi-thread. Como a variável set e os métodos get modificados por atomic adicionarão um bloqueio de rotação, se for uma cena com leitura frequente, o bloqueio de rotação consumirá mais recursos da CPU. Felizmente, não há muitos cenários de uso de lottieData combinados com análise de negócios e isso não causará desperdício excessivo de recursos da CPU. Além disso, você pode modificar a lógica do código para declarar lottieData como uma variável temporária e usar a variável temporária como o valor de retorno da função para resolver o problema de competição multithread.

3. Exceção de chamada de método

Para a variedade caleidoscópica de falhas, as exceções de chamada de método são um dos problemas mais fáceis de corrigir. No aplicativo, alguns métodos não são implementados por negligência e alguns métodos de objeto não existem devido à liberação de memória. Não discutiremos a solução, porque os desenvolvedores já a conheceram e resolveram várias vezes. Aqui, exploramos principalmente o processo de lançamento de exceção "seletor não reconhecido enviado para a instância".

Todos nós sabemos que a essência de chamar o método de um objeto em OC é enviar uma mensagem para o objeto. As chamadas de método são convertidas em funções objc_msgSend durante a compilação. O primeiro parâmetro obrigatório é o receptor da mensagem, o segundo parâmetro obrigatório é o nome do método, seguido pelos parâmetros passados.
objc_msgSend(id self, SEL op, … )

Aqui estão os passos para enviar uma mensagem:

(1) Detecta se o seletor precisa ser ignorado. Se houver um mecanismo de coleta de lixo em um sistema Mac OX, a função reter/liberar será ignorada.
(2) Verifique se o objeto de resposta é nulo. Enviar uma mensagem para um objeto nulo será ignorado pelo sistema de tempo de execução.
(3) A partir do cache, é realizado através do método de busca do ponteiro da função IMP e, se existir, o método é executado.
(4) Se não puder ser encontrado no cache, ele pesquisará na lista de métodos da classe e pesquisará recursivamente na lista de métodos da classe pai.
(5) Se nenhum for encontrado, entre na resolução do método dinâmico e no processo de encaminhamento de mensagens.

insira a descrição da imagem aqui
Envie uma mensagem para um objeto por meio da função objc_msgSend.Se o objeto não puder ser processado após vários processos, uma exceção será lançada. Antes de travar, o sistema de tempo de execução do OC passará pelas duas etapas a seguir:

  • DynamicMethod Resolution (Resolução de método dinâmico)
    toma um método de objeto como exemplo. O sistema chamará resolveInstanceMethod: para adicionar dinamicamente um método para o objeto. Se o valor de retorno for sim, ele procurará o método de instância novamente. Se o valor de retorno for sim for Não, ele entrará no processo de encaminhamento de mensagens.
+ (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];
}

O exemplo acima adiciona dinamicamente a implementação (openNewPage:) ao método handleOpenPage: do objeto de instância. Onde "v@:" representa o valor de retorno e os parâmetros. O significado de cada caractere pode ser visualizado em Codificações de tipo. (Link de codificação de tipo: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)

  • Encaminhamento de mensagem (encaminhamento de mensagem)
    o encaminhamento de mensagem chamará o método forwardingTargetForSelector para obter um novo destino como receptor e executar novamente o seletor. Se for um método de objeto, ele precisa ser substituído - método (id)forwardingTargetForSelector:(SEL)aSelector. Se for um método de classe, substitua o método + (id)forwardingTargetForSelector:(SEL)aSelector.
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(handleOpenPage:)){
        return _otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

Se o objeto retornado for inválido (nulo ou igual ao do antigo receptor), entre no processo forwardInvocation. Este método pode ser substituído no programa para definir a lógica de encaminhamento. O parâmetro anInvocation é o objeto gerado quando o sistema de tempo de execução chama o método methodSignatureForSelector: para obter a assinatura do método. Ao reescrever forwardInvocation:, você também deve reescrever o método methodSignatureForSelector:, caso contrário, uma exceção será lançada.

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

Todo o processo de encaminhamento de mensagens é mostrado na figura:

insira a descrição da imagem aqui
Quando um objeto não implementa o método correspondente, o sistema de tempo de execução notificará o objeto por meio da mensagem forwardInvocation. Cada objeto herda o método forwardInvocation: da classe NSObject, e a implementação do método em NSObject simplesmente chama doesNotRecognizeSelector:. Ao implementar nosso próprio método forwardInvocation:, podemos encaminhar mensagens para outros objetos na implementação do método. Se não for tratado nesses processos, uma exceção será lançada e ocorrerá uma falha.

Os itens acima são casos típicos, código-fonte e análise de princípios de aplicativos financeiros no processo de gerenciamento de falhas. No processo de gerenciamento de caso acima, a equipe de P&D acumulou uma experiência muito útil na detecção de problemas.
4. Precipitação da experiência real de combate

1. O caminho da operação do usuário é o melhor prompt para reproduzir o problema.As informações da pilha no log de travamento podem rastrear em quais páginas o usuário entrou, em que estado está, se está sendo executado em primeiro plano ou em segundo plano. Combinado com a plataforma de monitoramento de desempenho APM, o estado atual do aplicativo pode ser analisado com mais clareza. De acordo com o ID de inicialização, você pode ver o status da rede do aplicativo antes da falha e quais solicitações de rede foram enviadas. Isso pode ajudar os desenvolvedores a reproduzir problemas mais rapidamente.

2. A falha ocorre no código comercial do próprio aplicativo, o que geralmente é fácil de resolver. Se ocorrer na biblioteca de código aberto de terceiros usada, você pode primeiro acessar a comunidade de código aberto para encontrar o mesmo problema. Alguns bibliotecas de código aberto mantidas com frequência passaram por muitos cenários de aplicativos reais Teste, haverá problemas e soluções semelhantes. Se for resolvido pela equipe de P&D, você também pode enviar uma solicitação pull relacionada na comunidade de código aberto. E compartilhe a experiência de resolvê-lo com desenvolvedores que encontram o mesmo problema.

3. Qualquer travamento tem condições específicas para sua ocorrência. Quando for realmente impossível de reproduzir, deve-se procurar uma solução por outro ângulo. Ler o código-fonte é uma forma muito, muito boa de entender a natureza do problema. O sistema da Apple é uma ecologia de circuito fechado, mas alguns O código-fonte é de código-fonte aberto, você pode lê-lo on-line ou baixar o código-fonte relevante para uma compreensão aprofundada.

4. O log de travamento é a informação em primeira mão para resolver o problema de travamento. O log de travamento contém as informações da pilha e a causa do travamento quando o aplicativo trava. A leitura dos logs de travamento é muito importante no desenvolvimento. Portanto, é necessário apresentar brevemente o conteúdo do log de travamento na seção a seguir.

Cinco, análise de log de falhas

1. Conteúdo do registro de falhas

Depois que ocorre um travamento, a primeira coisa em que pensamos é em qual linha de código está o travamento, qual é a pilha e quais threads estão em execução, todos incluídos no relatório do travamento. Tomando o Demo na WWDC como exemplo, o ChocolateChip roda no emulador. A parte superior do log de falha contém algumas informações resumidas, incluindo o nome do aplicativo, número da versão, sistema operacional e a data e hora da falha.

insira a descrição da imagem aqui
A parte seguinte é a causa do travamento. O erro ocorre na thread principal. O tipo de travamento é SIGILL, ou seja, a CPU está executando uma instrução inexistente ou inválida. O motivo específico para a falha exibida em Erro fatal é forçar a descompactação de uma variável com um valor opcional nulo.

insira a descrição da imagem aqui
A próxima parte são as informações da pilha da falha, você pode verificar o thread atual com falha, as informações da pilha no momento da falha, etc.

As informações da pilha original da falha são mostradas na figura a seguir:

insira a descrição da imagem aqui
As informações da pilha original da falha são inconvenientes para localizar diretamente o problema da falha, e as informações da pilha original precisam ser simbolizadas (). O processo de conversão de endereços de memória em nomes de métodos, nomes de arquivos e números de linhas é chamado de simbolização. Existem 3 elementos necessários para a simbolização do log de travamento.

(1) Crash logs, crash logs podem ser obtidos no painel Crashes abrindo a janela do Organizer na opção Window do Xcode, ou baixados do plano de fundo do envio do App. Aplicativos com recursos completos de monitoramento podem coletar e relatar ao servidor para armazenamento por meio do aplicativo e, em seguida, baixar logs de falhas do servidor.

(2) Tabela de símbolos. dSYM (símbolos de depuração) também é chamado de tabela de símbolos de depuração. Todas as aplicações compiladas e carregadas através do Xcode serão automaticamente arquivadas, abra a janela Organizer na opção Janela do Xcode, e os arquivos compilados da aplicação serão exibidos no menu Arquivos. Selecione o arquivo para ver o arquivo dsym do aplicativo por meio de mostrar no localizador → exibir o conteúdo do pacote.

(3) O caminho /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash pode obter a ferramenta simbólica que vem com o Xcode.

Copie os três arquivos acima no mesmo arquivo, verifique se os UUIDs dos três arquivos são consistentes, use o terminal para entrar no diretório atual e execute o comando simbólico ./symbolicatecrash-vxxxx.crash xxxx.app.dSYM. Abra o arquivo xxxx.crash após a conclusão, você pode ver a pilha simbolizada e pode ver claramente o nome do método, nome do arquivo e número da linha e outras informações. As informações simbolizadas são mostradas na figura abaixo:

insira a descrição da imagem aqui
Obviamente, haverá algumas informações de baixo nível na parte inferior do log, incluindo o status do registro do thread com falha e a imagem de dados binários carregada no processo, que são os dados do arquivo executável do aplicativo. O Xcode procura símbolos, arquivos e informações de número de linha por meio de simbolização e os exibe na pilha.

Informações de registro:
insira a descrição da imagem aqui
Imagem de arquivo executável:
insira a descrição da imagem aqui
Acima está todo o conteúdo de um log de falha, e as seguintes informações úteis devem ser observadas neste conteúdo.

Primeiro, comece com o tipo de travamento.No exemplo, o tipo de exceção é a exceção EXC_BAD_INSTRUCTION e a CPU está executando uma instrução ilegal. A mensagem de falha informa que a causa da falha foi a descompactação forçada de um objeto opcional.

Em segundo lugar, a falha está no thread principal e a pilha contém a pilha de funções que estava em execução no momento da falha. A função fatalErrorMessage é vista na pilha.Esta é uma função do sistema, e uma função no código a chama.

Como você pode ver no rastreamento de pilha (RecipeImage.swift:26), a chamada ocorre na linha 26 do arquivo RecipeImage.swift. No código existe uma classe Recipe cuja função image é chamada e essa função chama a função fatalErrorMessage devido a algum erro. Ao obter a imagem, o código descompacta o caminho opcional à força, causando um travamento. Veja abaixo:

insira a descrição da imagem aqui

2. Onde visualizar o log de falhas

Depois de interpretar o conteúdo do log de travamento, onde posso visualizar o log de travamento

(1) Use o AppleID para fazer login no Xcode e visualize o item de travamento no organizador na barra de menus.
(2) Se você conseguir obter a máquina com falha, poderá obter diretamente as informações de log no dispositivo e filtrar as informações de log relacionadas ao aplicativo.
(3) Plataforma de monitoramento de aplicativos, que coleta informações de falhas por meio do lado do aplicativo e as classifica e analisa em segundo plano. É mais conveniente ajudar a desenvolver e localizar problemas.

6. Estratégia especial de governança do Crash

1. Configure um projeto de colisão especial e defina uma meta em fases

Antes da governança especial do JD Financial App travar, a taxa de travamento do aplicativo era instável e flutuava com o lançamento de versões e iterações de negócios. Existem muitos tipos de problemas de travamento nas estatísticas online. Existem falhas simples de matriz fora dos limites, falhas ao inserir valores nil e problemas de memória que causam ponteiros selvagens e exceções multithread. Em resposta a essas situações, a equipe montou uma equipe especial para gerenciamento de falhas, classificada de acordo com o número de falhas e urgência, e resolveu ciclicamente os dez principais problemas da lista de falhas.

2. Localização e distribuição do módulo de travamento

O aplicativo atual não está mais limitado a um determinado negócio, mas já é uma coleção de várias funções de negócios. Depois que todo o aplicativo é dividido em componentes, os códigos de cada função de negócios são integrados no aplicativo na forma de .a, .framwork, etc. Quando ocorre um travamento, fica difícil descobrir em qual módulo de negócio está o código do travamento, o que causa grandes dificuldades na distribuição e resolução do travamento. Com base nesse problema, a força-tarefa de travamento executa a correspondência de nome de arquivo por meio de linkmap ou usa o comando grep para localizar arquivos .a e .framework que contenham códigos de travamento no binário. Se a pesquisa for bem-sucedida, ela será enviada a todas as partes comerciais de maneira conveniente para processamento oportuno.

3. Estabeleça um sistema de monitoramento de aplicativos

No processo de resolução de falhas anterior, o P&D tomou a iniciativa de acessar o plano de fundo do desenvolvedor da Apple ou o plano de fundo do monitoramento de falhas de terceiros (bugly, Youmeng, etc.) para verificar a tendência atual de falhas, etc. conduzido e tem um atraso , Por exemplo, é difícil responder em tempo hábil a um grande número de falhas que ocorrem repentinamente ao alterar as configurações em segundo plano. O aplicativo financeiro monitora a tendência de falhas por meio do sistema de monitoramento de desempenho APM, define o limite de falhas e aciona automaticamente um alarme dentro de um número especificado de falhas em um determinado período de tempo e aciona um alarme se a taxa de falhas exceder o limite para um determinado período de tempo, e envia e-mails, ferramentas de comunicação interna e outros meios para notificar o responsável para tratá-lo em tempo hábil. Ao mesmo tempo, relatórios semanais de desempenho são enviados automaticamente para avaliar o desempenho do sistema.

4. Continue prestando atenção aos problemas existentes e reduza os problemas emergentes

Deve haver alguns problemas no desenvolvimento diário que são persistentes e difíceis de reproduzir. Esse tipo de problema tem um pequeno número de travamentos quando a atividade diária está estável, mas continua a ser executado em várias versões. Pode ser submerso em outras falhas em horários normais, mas quando a atividade diária, como 618 e Double Eleven, aumenta muito , o número de falhas aumentará. Este também é um período especial para descobrir problemas. Além disso, ao resolver os problemas originais, concentre-se em monitorar o novo negócio depois que ele entrar no ar. No caso de problemas que não aparecem em Grayscale, ele travará e explodirá quando mais usuários o usarem.

5. Especificação de codificação

Boas práticas de codificação ajudam a reduzir erros de codificação. Os algoritmos complexos e a lógica do programa são apenas uma pequena parte do programa, e a maioria das falhas pode ser evitada por meio da revisão do código. Matrizes de dicionário convencionais inserem valores nulos, métodos não podem ser encontrados, etc. foram basicamente eliminados após o desenvolvimento, teste e escala de cinza.

7. Resumo

Este artigo se concentra no conhecimento básico de travamentos, incluindo a ocorrência de travamentos, cenários típicos de travamentos e como interpretar os logs de travamento. Ao mesmo tempo, combinado com os exemplos reais de travamento encontrados no desenvolvimento de aplicativos financeiros, será analisado em detalhes desde a causa do problema, como localizar o local do travamento, como reproduzi-lo e como repará-lo. Durante o desenvolvimento, você pode localizar o problema de acordo com o tipo de travamento e o log de travamento e fornecer uma solução para travamentos típicos. O desempenho do aplicativo e a experiência do usuário são um processo de otimização de longo prazo. As falhas não param com a otimização. Somente atenção e otimização contínuas podem fazer com que os aplicativos de hoje que explodiram em volume de código avancem de forma constante.

Na sexta parte deste artigo, o sistema de monitoramento de desempenho APM é mencionado. O sistema de monitoramento de desempenho APM é uma plataforma de monitoramento de desempenho construída pela equipe móvel da JD Technology e pela equipe de operação e manutenção. Leva tempo para iniciar, flutua nas solicitações de rede, e leva tempo para abrir o webView. , Rastreamento de usuário, monitoramento de página nativa, congelamento de travamento, monitoramento personalizado e outras funções estão disponíveis, obtendo monitoramento de link completo desde a inicialização até a conexão do servidor até a saída. Atualmente, vários aplicativos da JD Technology foram conectados ao sistema de monitoramento de desempenho APM, que fornece maior garantia de qualidade para cada aplicativo e equipe de negócios.

Autor deste artigo: Jingdong Technology Wu Xinyu
Para obter mais práticas recomendadas e inovações técnicas, preste atenção à conta oficial do WeChat de "Jingdong Technology Technology Talk"

insira a descrição da imagem aqui

Acho que você gosta

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