Aceleração do processamento em lote de dados (coletor GC -> ponto seguro -> pool de threads)

I. Introdução

       Há um sistema de espelhamento de dados que executa o processamento em lote para gerar tabelas diárias às 0:00 todos os dias. Como a quantidade de dados continua a aumentar, o tempo de processamento mudou de uma hora antes para quase duas horas por dia. Uma vez que o atraso continua , muitas tarefas como BI e inventário serão afetadas. Os líderes permitem que os blogueiros otimizem e acelerem.

        Os blogueiros analisam e praticam de várias direções, como coletores de GC, colocação de ponto seguro de ciclos contáveis ​​e colocação de CPU e thread.

2. Acelere a direção

Primeiro veja o código

List<A> as = mapper.get(queryDTO, i);
                if (CollectionUtils.isEmpty(as )) {
                    break;
                }
                /**------------**/
                CountDownLatch latch;
                if (as .size() < paramConfig.getInsertBatchQuantity()) {
                    latch = new CountDownLatch(1);
                } else if (as .size() % paramConfig.getInsertBatchQuantity() == 0) {
                    latch = new CountDownLatch(assetCollects.size() / paramConfig.getInsertBatchQuantity());
                } else {
                    latch = new CountDownLatch(as .size() / paramConfig.getInsertBatchQuantity() + 1);
                }
                Lists.partition(as , paramConfig.getInsertBatchQuantity()).forEach(a -> {
                    Runnable runnable = () -> {
                        try {
                            log.info("线程" + Thread.currentThread().getName() + "开始执行");
                            mapper.batchSave(a, tableName);
                            log.info("线程" + Thread.currentThread().getName() + "执行完成");
                        } catch (Exception e) {
                            log.error("数据镜像执行异常", e);
                        }
                        finally {
                            latch.countDown();
                        }
                    };
                    dbInsertExecutorService.execute(runnable);
                });

                latch.await();

        É verificar os dados e inseri-los na tabela diária simultaneamente. A simultaneidade original é baseada no número de consultas/inserções. Ao inserir, você precisa usar o sincronizador CountDownLatch para aguardar a conclusão da inserção.

1、GC

        No começo, eu não queria mexer no código, então tentei ver se ele pode ser acelerado a partir da instrução de consulta de dados e do gc. A consulta sql não é complicada e não há nada a dizer.

        A frequência de coleta é em torno de 60, o menor gc é em torno de 30ms e o maior gc é em torno de 200ms. Na verdade, é compreensível. Afinal, os dados estão sendo acessados ​​constantemente e a cadeia de referência será perdida em breve, por isso é normal ter reciclagem intensiva.

        

         Então é possível diminuir o tempo do gc parar o mundo?Olha a configuração do jvm

javaagent:/app/skywalking/agent/skywalking-agent.jar -jar -Djava.security.egd=file:/dev/./urandom -server -Xms3998m -Xmx3998m -XX:MetaspaceSize=200m -XX:+UseGCLogFileRotation -XX :NumberOfGCLogFiles=3 -XX:GCLogFileS
ize=10m -Xmn1999m -Xloggc:/dev/shm/gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:SurvivorRatio=8 -XX: -UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/ /app/code/app.jar

        Não há nenhuma configuração especial para o coletor de lixo, ou seja, o UseParallelGC padrão do jdk8. Este par de coletores precisa parar o mundo durante o processo de coleta. Atualmente, CMS e G1 são usados ​​principalmente para coleta simultânea, respectivamente por meio do método incremental método de atualização e instantâneo original resolve o problema de marcação de mapa de três cores, de modo a realizar a coleta simultânea.

        Coordenar com o departamento de operação e manutenção para modificar a configuração jvm, usando G1 para ver a velocidade de processamento, o resultado está preso na primeira etapa, a operação e manutenção disse que todas as configurações jvm do sistema são extraídas do mesmo caminho de montagem do Linux quando gerando a imagem de lançamento As alterações nos arquivos afetarão outros sistemas. Os blogueiros não sabem muito sobre os arquivos de configuração do K8S e os mecanismos de espelhamento de lançamento, então não podem refutá-los. Já houve tempos semelhantes antes, e esta é a perda da ignorância. Havia muitas outras coisas para pesquisar antes, e agora tenho energia para estudar contêineres. O blogueiro comprou secretamente "Entendimento profundo do K8S" e planeja encontrar todos os lugares mais tarde.

2. Ponto seguro

         O coletor não consegue passar, vamos ver outras soluções. Em "Entendendo em profundidade a Java Virtual Machine", o autor mencionou que o jvm não inserirá um ponto seguro no loop contável. Se o corpo do loop executar um operação demorada, causará a necessidade de esperar que o thread entre no ponto seguro por muito tempo durante o processo de coleta do GC

       

        Então, o símbolo da função não atende a essa situação. Pode-se ver que a segmentação do loop é um loop contável e o corpo do loop é abrir um thread para inserir no banco de dados. Então, a rede io + banco de dados io pode ser calculada por mais tempo do que código comum, então reescreva-o Experimente, mas o resultado final é apenas algumas dezenas de segundos mais rápido, e isso também é passado.

3. CPU e thread

        No passado, os desenvolvedores usavam o pool de threads para habilitar multithreading e, em seguida, usavam o sincronizador para bloquear o processo. Por que não remover o sincronizador? Se eles temem que os dados na fila possam ser perdidos devido ao tempo de inatividade e outros motivos , então o tempo de inatividade é originalmente Por que o design que precisa ser reinserido depois de esvaziar a mesa?

        Vamos dar uma olhada na configuração do pool de threads aqui. Use ThreadPoolExecutor para gerar um pool de threads personalizado. Configure uma fila limitada de acordo com a especificação de uso. O número de filas é 10. O problema está na estratégia de rejeição. O padrão estratégia de rejeição é descartar e lançar uma exceção.Adivinhe o desenvolvimento anterior O pessoal não sabe muito sobre a estratégia de rejeição do pool de threads e não pensou em configurar outras estratégias.

new ThreadPoolExecutor(poolProperties.getCorePoolSize(), poolProperties.getMaxPoolSize(),
                poolProperties.getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(poolProperties.getMaxQueueSize()), factory);
    

         O blogueiro define a política de rejeição do pool de threads para a execução do thread principal, que também é comumente usado por blogueiros durante o desenvolvimento.Eu sinto que nenhuma empresa pode perder dados de mensagens.

new ThreadPoolExecutor(poolProperties.getCorePoolSize(), poolProperties.getMaxPoolSize(),
                poolProperties.getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(poolProperties.getMaxQueueSize()), factory, new ThreadPoolExecutor.CallerRunsPolicy());
    

        Em seguida, remova o sincronizador

//                CountDownLatch latch;
//                if (as.size() < paramConfig.getInsertBatchQuantity()) {
//                    latch = new CountDownLatch(1);
//                } else if (as.size() % paramConfig.getInsertBatchQuantity() == 0) {
//                    latch = new CountDownLatch(as.size() / paramConfig.getInsertBatchQuantity());
//                } else {
//                    latch = new CountDownLatch(as.size() / paramConfig.getInsertBatchQuantity() + 1);
//                }
                Lists.partition(as, paramConfig.getInsertBatchQuantity()).forEach(a-> {
                    Runnable runnable = () -> {
                        try {
                            log.info("线程" + Thread.currentThread().getName() + "开始执行");
                            assetService.batchSave(a, tableName);
                            log.info("线程" + Thread.currentThread().getName() + "执行完成");
                        } catch (Exception e) {
                            log.error("数据镜像执行异常", e);
                        }
//                        finally {
//                            latch.countDown();
//                        }
                    };
                    dbInsertExecutorService.execute(runnable);
                });

//                latch.await();

        O pool de threads foi ajustado para 15, mas a velocidade caiu um pouco. Depois de pensar nisso, isso é intensivo em io (rede io + banco de dados io) e o thread não tem nenhum cálculo, então a cpu apenas executa uma divisão submissão e, em seguida, espera io, alternância de contexto mais bloqueios auto-aumentáveis ​​de chave primária do banco de dados podem consumir muito tempo.

        Ajuste o encadeamento e a fila para 6, a velocidade aumentou e o ambiente de teste aumentou em mais de dez por cento

Demora antes da otimização do ambiente de teste

depois da otimização

        olhe novamente online

       antes da otimização on-line

 Depois da otimização on-line

         Pode-se ver que a velocidade online é aumentada em mais de 50%.

 3. Resumo

         Para otimizar e acelerar, o blogueiro começa com o coletor gc->problema de ponto de segurança de ciclo contável->ajuste do conjunto de threads, análise multidimensional e espera que os alunos possam ver mais dos princípios, como o problema do ponto de segurança em alguns de alta precisão A possibilidade de otimização de aceleração no nível ms, o conhecimento jvm envolvido no coletor pode ser usado para muitos problemas de otimização e solução de problemas, sugiro que você leia "Entendimento profundo da máquina virtual java", você vai ganhar muito.

Acho que você gosta

Origin blog.csdn.net/m0_69270256/article/details/128558064
Recomendado
Clasificación