Java開発 - プログラムパフォーマンスの最適化方法


バックエンド開発で学ぶべき最適化の考え方!

1. アルゴリズムロジックの最適化

(1) どのようなアルゴリズムの最適化ですか?
アルゴリズムの最適化の考え方は、同じ問題を解決するためにより良い方法を選択することです。

(2) アルゴリズム最適化の詳細説明

  アルゴリズムの最適化はパフォーマンス最適化のキーポイントと言えます。暴力的な O(n 2 ) と最適化された O (n*log(n)) のように、優れたアルゴリズムと従来のアルゴリズムの間には桁違いの違いがあることがよくあります。 , in 1000 データ量の差は約 1,000 倍、データ量が 1,000,000 の場合は 1,000,000 倍になります。性能差は電力増加です。データ量が大きくなるほど差は顕著になります。

  当初は1秒で結果が得られましたが、現在では結果が出るまでに100万秒と約12日を要しており、アルゴリズムの最適化がなければ基本的にサービスが利用できないことが分かります。

  アルゴリズムは常に変化しており、問題ごとに異なる手法を採用する必要があるため、複雑な論理問題によっては、専門のアルゴリズム エンジニアがソリューションを設計する必要がある場合があります。

2. Redisキャッシュの最適化

(1) Redis キャッシュ最適化の原理は、
  ユーザーの最初のクエリの結果をキャッシュし、次のクエリが行われたときにプログラムを実行せず、キャッシュ内のデータを直接取得します。
ここに画像の説明を挿入

  データをクエリするとき、データベースは複数回クエリされることがよくあり、データをカプセル化するときに特定のアルゴリズム処理が必要になる場合があります。したがって、Redis キャッシュに直接データを取得する方が、データベースにクエリを実行してデータを取得するよりもはるかに高速になります。

(2) Redis キャッシュ最適化の小さな例
1. Spring Boot プロジェクトを作成し、Web と Redis の依存関係をインポートし、application.yml ファイルを構成し、MyCacheConfig.java ファイルを構成します。

pom ファイルは以下に依存します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wu</groupId>
    <artifactId>hello</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello</name>
    <description>hello</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
<!--        热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
<!--        web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        spring使用缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
<!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>


</project>

ここに画像の説明を挿入

application.yml ファイルは次のように構成されています。

server:
  port: 8080

spring:
  redis:
    host: 47.115.230.86
    port: 6379

  cache:
    type: redis
    redis:
      time-to-live: 300000
      cache-null-values: true
ribbon:
  eager-load:
    enabled: true
    clients: eureka-provider

MyCacheConfig.java ファイルの構成は次のとおりです。

package com.wu.hello.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
    
    
    
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
    
    

        RedisCacheConfiguration config  = RedisCacheConfiguration.defaultCacheConfig();
        //设置key用string类型保存,value用json格式保存
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //使配置文件中所有的配置都生效
        if (redisProperties.getTimeToLive() != null) {
    
    
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
    
    
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
    
    
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
    
    
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

2. HelloControllerプログラムを作成し、プロジェクトを開始します。

package com.wu.hello.controller;


import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    

//    springCacheable可以是实现查询redis,如果有直接返回数据,没有再查
    @Cacheable(value ={
    
    "HelloApplication"},key = "#root.methodName" )
    @RequestMapping("hello")
    public String hello() throws InterruptedException {
    
    
        System.out.println("要等十秒咯");
        Thread.sleep(10000);//睡眠十秒
        return "你好啊!";
    }
}

ここに画像の説明を挿入

3. 可視化ツールを使ってredisに接続
ここに画像の説明を挿入
4. 最初のアクセスでわかるまでに10秒かかる
ここに画像の説明を挿入
ここに画像の説明を挿入
5. redisのほうがデータが多いことがわかり、後からリクエストすると以下のリクエストが届く!
ここに画像の説明を挿入
ここに画像の説明を挿入

3. 非同期オーケストレーション

(1) 非同期オーケストレーションとは何ですか?
  プログラムは元の結果を変更せずに非同期で実行されます。
ここに画像の説明を挿入
ここに画像の説明を挿入

(2) 非同期配置の小さな例
1. redis の例に従って、application.properties ファイルを設定します。


ikun-thread.core-size=20
ikun-thread.max-size=200
ikun-thread.keep-alive-time=10

ここに画像の説明を挿入

2. ThreadPoolConfigProperties.java 構成ファイルを追加します

package com.wu.hello.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "ikun-thread")
@Component
@Data
public class ThreadPoolConfigProperties {
    
    
    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAliveTime;
}

ここに画像の説明を挿入

3. MyThreadConfig.java 構成ファイルを追加します。

package com.wu.hello.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class MyThreadConfig {
    
    

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){
    
    
        return new ThreadPoolExecutor(pool.getCoreSize(), pool.getMaxSize(), pool.getKeepAliveTime(), TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }


}

ここに画像の説明を挿入

5. HelloController.javaを以下のプログラムに変更します。

package com.wu.hello.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.xml.crypto.Data;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

@RestController
public class HelloController {
    
    


    @Autowired
    ThreadPoolExecutor executor;

    @RequestMapping("hello1")
    public String hello1() throws InterruptedException {
    
    
        Date date1 = new Date();
        Thread.sleep(5000);
        Thread.sleep(5000);
        Thread.sleep(5000);
        Thread.sleep(5000);
        Date date2 = new Date();
        long t = (date2.getTime() - date1.getTime())/1000;
        return "您一共等待了"+t+"秒";
    }

    @RequestMapping("hello2")
    public String hello2() {
    
    


        Date date1 = new Date();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> sleep1 = CompletableFuture.runAsync(() -> {
    
    
            try {
    
    
                Thread.sleep(5000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, executor);
        CompletableFuture<Void> sleep2 = CompletableFuture.runAsync(() -> {
    
    
            try {
    
    
                Thread.sleep(5000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, executor);
        CompletableFuture<Void> sleep3 = CompletableFuture.runAsync(() -> {
    
    
            try {
    
    
                Thread.sleep(5000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, executor);
        CompletableFuture<Void> sleep4 = CompletableFuture.runAsync(() -> {
    
    
            try {
    
    
                Thread.sleep(5000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, executor);
        CompletableFuture.allOf(sleep1,sleep2,sleep3,sleep4).join();
        Date date2 = new Date();
        long t = (date2.getTime() - date1.getTime())/1000;
        return "您一共等待了"+t+"秒";
    }

}

ここに画像の説明を挿入

6. 従来型と非同期型の違いを見てください。
ここに画像の説明を挿入
ここに画像の説明を挿入
従来型では 20 秒かかりますが、非同期ではわずか 5 秒しかかからないことがわかります。

4. MQ は山を切り取り、谷を埋める

(1) MQ の最適化原理
  MQ(Message Queue)のメッセージキューは,緊急性の高いリソースを先に処理し,すぐに処理する必要のないリクエストを先にメッセージキューに入れて,アイドル状態になったときに処理します。
  例えば、タオバオや京東などの商品は秒単位で売れますが、トラフィックが一瞬で大きすぎて、請求や在庫削減、ユーザーポイントデータの更新などのすべてのプロセスが完了すると、ユーザーは非常に行き詰まりを感じてしまいます。 、そしてそれはそれほど長くはありませんでした。MQ の最適化後、ユーザーが送信した情報を受け取り、すぐに受け取ったことをユーザーに伝え、要求されたデータをメッセージ キューに保存し、サーバー トラフィックが減少したときに請求、在庫の削減、ユーザー ポイントの更新を行いました。低いデータ。

(2) MQ 最適化の小さな例
1. 遅延キュー。seckill アクティビティが 1 分で終了すると仮定すると、課金、在庫削減、およびユーザー ポイント データの更新を実行するまで 1 分待つことができます。

2. メッセージキューの依存関係を追加する

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

ここに画像の説明を挿入

3. MQ 構成ファイル MyRabbitConfig.java を追加します。

package com.wu.ikun.skill.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class MyRabbitConfig {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initRabbitTemplate(){
    
    
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
    
    
//                System.out.println("confirm...correlation["+correlationData+"]==>ack["+b+"]");
            }
        });

        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
    
    

            }
        });
    }


    @Bean
    public MessageConverter messageConverter(){
    
    
        return new Jackson2JsonMessageConverter();
    }
}

4. MQ 構成ファイル MyCacheConfig.java を追加します。

package com.wu.hello.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
    
    

    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
    
    

        RedisCacheConfiguration config  = RedisCacheConfiguration.defaultCacheConfig();
        //设置key用string类型保存,value用json格式保存
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //使配置文件中所有的配置都生效
        if (redisProperties.getTimeToLive() != null) {
    
    
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
    
    
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
    
    
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
    
    
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

5. 構成ファイル application.yml を変更します。

server:
  port: 8080

spring:
  redis:
    host: 47.115.230.86
    port: 6379

  cache:
    type: redis
    redis:
      time-to-live: 300000
      cache-null-values: true


  rabbitmq:
    host: 47.115.230.86
    port: 5672
    virtual-host: /
    template:
      mandatory: true
    publisher-returns: true
    publisher-confirm-type: simple
    listener:
      simple:
        acknowledge-mode: manual


ribbon:
  eager-load:
    enabled: true
    clients: eureka-provider

6. HelloController.javaを変更する

package com.wu.hello.controller;

import com.rabbitmq.client.Channel;
//import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.Date;

@RestController
public class HelloController {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;



    @RabbitListener(queues = "order.release.order.queue")
    public void listener(String msg, Channel channel, Message message) throws IOException {
    
    
        System.out.println("秒杀时间过来,开始开单、减库存、更新用户积分数据"+msg+ "----" + new Date().toString());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

    }

    @ResponseBody
    @RequestMapping("/create/order")
    public String createOrderEntity(){
    
    

        for (int i = 0; i < 10; i++) {
    
    
            rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",i + " " + new Date().toString());
        }
        return "创建10个订单成功!";
    }

}

7. サービスを開始すると、MQ が構成されたスイッチ、キューなどを作成したことを確認します。
ここに画像の説明を挿入

8.
ここに画像の説明を挿入
ユーザーが請求書を発行するシミュレーションを 9 分または 1 分行った後、バックグラウンドがユーザー情報を受信し、請求などを開始します。
ここに画像の説明を挿入

5. プリロード

(1) プリロード最適化の原理は、
  ユーザーがリクエストを送信することを推測し、ユーザーが注目する前に、まずデータを見つけてキャッシュに入れるため、ユーザーが実際にデータをリクエストしたとき、非常に速く感じられます。
ここに画像の説明を挿入

6. SQLチューニング

(1) SQL チューニングの原則
この最適化には多くの側面があります:
SQL コード: SQL の クエリにジョイント テーブルを断固として使用しない ストレージ
: インデックス作成、実際には最下層は時間のためのスペースを使用し、b+ ツリーは時間の複雑さを軽減します。 O(n) から O(log(n)) に最適化され、
プログラミングの観点から 一桁最適化されます。ループ内でテーブルを検索することは絶対にありません。単一のデータが必要な場合は、最初にすべてのデータを取り出し、マップに保存し、使用するときにマップ内で検索することで、時間計算量を O(n 2 ) から O(n*log( n )) に最適化できます。 、桁違いに最適化され、データベースの接続時間が最適化されます。

7.JVMチューニング

(1) jvm チューニングの原理
  現状に最適な jvm の動作パラメータを見つけるために最も重要なことはメモリの割り当てです。メモリ割り当てが小さすぎると、ガベージ コレクション メカニズム (ガベージ コレクション) が頻繁にトリガーされ、プログラムのパフォーマンスが低下します。メモリ割り当てが大きすぎるため、リソースが大幅に浪費されます。

(2) jvm の概要
jvm (Java Virtual Machine)、Java 仮想マシン

Java仮想マシンは、実際のコンピュータ上でソフトウェアシミュレーションによって実現される架空のマシンです。Java 仮想マシンには、プロセッサ、スタック、レジスタなどの独自の仮想ハードウェアがあり、対応する命令システムもあります。

jvm のメモリ モデル:
ここに画像の説明を挿入

ヒープのガベージ コレクション パーティション:
ここに画像の説明を挿入

gc プロセス:
ここに画像の説明を挿入

(3) jvm チューニング ステップ
1、サービスの平均訪問回数を予測します (例: 300 回)

2. サービスの最大メモリを 100M に設定します
ここに画像の説明を挿入

3. インターフェイスを作成し、2 秒間スリープして、サービスを開始します

ここに画像の説明を挿入

4. テスト ツール JMeter を使用して、Hello インターフェイスで 300 のスレッドを開始します。各スレッドには無制限のリクエストがあります。
ここに画像の説明を挿入

5. jvisualvm を使用してサービス gc のステータスを確認します。
ここに画像の説明を挿入

ここに画像の説明を挿入

gc データ、ヒープ メモリの変更などを確認し、ストレス テストの結果に従って適切な jvm パラメータのセットを見つけ続けることができます。

ここに画像の説明を挿入
JMeter では、リクエストのスループット、エラー率などのリクエストに関する情報を確認できるため、どの部分を最適化できるかをより適切に分析できます。

8. クラスター構築

(1) クラスター構築最適化の原理により、
  サーバー上で複数の同一のサービスが起動され、これらのサービスはすべて同じことを実行できます。ユーザーが要求すると、プロキシ アルゴリズムに従って、比較的アイドル状態のサービスに割り当てて実行することができ、サーバーの負荷の問題を解決します。
ここに画像の説明を挿入

テストプロジェクトのソースリンク: https://pan.baidu.com/s/11LUO38lmnp7lBQTQaxCRcw ?pwd=fkdu
抽出コード: fkdu

悪くないと思ったら高評価お願いします!

おすすめ

転載: blog.csdn.net/weixin_52115456/article/details/131194115