分散メディア資産管理のアイデアと分散ファイルおよびタスク呼び出しシステムの紹介

1.モジュールの紹介

現在、メディア資産管理の主な管理対象はビデオ、写真、ドキュメントなどで、メディア資産ファイルのクエリ、ファイルのアップロード、ビデオ処理などが含まれます。

メディア資産のクエリ: システムは、所有するメディア資産情報をクエリできます。

ファイルのアップロード: 写真のアップロード、ドキュメントのアップロード、ビデオのアップロードを含みます。

ビデオ処理: ビデオは正常にアップロードされ、システムはビデオを自動的にエンコードします。

ファイルの削除: 教育機関が自らアップロードしたメディア ファイルを削除します。

2.業務プロセス

主に写真のアップロード、動画のアップロード、動画処理に分かれます。

2.1 画像アップロードプロセス

ここに画像の説明を挿入
1. フロントエンドで画像アップロード インターフェイスに入ります
2. 画像をアップロードし、メディア資産管理サービスをリクエストします。
3. メディア資産管理サービスは、画像ファイルをMinIO に保存します。
4. メディア資産管理は、ファイル情報をデータベースに記録します。
5. フロントエンドはコンテンツ管理サービスにコース情報の保存と画像アドレスのコンテンツ管理データベースへの保存を要求します。

2.2 ビデオのアップロードプロセス

ここに画像の説明を挿入
1.フロントエンドはファイルをチャンクに分割します
2. チャンク ファイルをアップロードする前に、フロント エンドはメディア アセット サービスにファイルが存在するかどうかを確認するように要求します。ファイルがすでに存在する場合、再度アップロードされることはありません。
3. チャンク ファイルが存在しない場合、フロントエンドはアップロードを開始します
4. フロントエンドはメディア アセット サービスにチャンクのアップロードを要求します。
5. メディア資産サービスは、分割して MinIO にアップロードされます。
6. フロントエンドは、アップロードの完了後にメディア資産サービスにチャンクを結合するよう要求します。
7. メディア資産サービスは、マルチパートのアップロードが完了したと判断すると、MinIO にファイルをマージするように要求します。
8.マージが完了したら、マージされたファイルが完了しているかどうかを確認します。完了している場合はアップロードが完了し、完了していない場合はファイルが削除されます。

2.3 ビデオ処理プロセス

(ここで使用されるビデオ トランスコーディング ツールは FFmpeg です)
ここに画像の説明を挿入 1. タスク スケジューリング センターがジョブ フラグメントをブロードキャストします
2. エグゼキュータはブロードキャスト ジョブ フラグメントを受信し、データベースから保留中のタスクを読み取り、未処理のタスクと失敗したタスクを読み取ります。
3. 実行者はタスクを処理中へ更新し、タスク内容に応じてMinIOから処理対象のファイルをダウンロードします
4. エグゼキュータはタスクを処理するために複数のスレッドを開始します。
5. タスクの処理が完了したら、処理されたビデオを MinIO にアップロードします
6.タスク処理結果が更新されますビデオ処理が完了すると、タスク処理結果の更新に加えて、ファイルのアクセスアドレスもタスク処理テーブルとファイルテーブルに更新され、最終的にタスク完了となります。レコードは履歴テーブルに書き込まれます。

3.データモデル

1.3 データ モデル
このモジュールのメディア アセット ファイルに関連するデータ テーブルは次のとおりです。
ここに画像の説明を挿入

メディア アセット ファイル テーブル: 写真、ビデオ、ドキュメントなどのファイル情報を保存します。
media_process : 処理対象のビデオ テーブル。トランスコード対象のビデオ データ (.avi->mp4 など) が保存されます。
media_process_history : 正常に処理されたビデオ情報を記録するビデオ処理履歴テーブル。

4. 使用される分散テクノロジーの大まかな紹介

今回使用した分散技術はMinIoXXL-Jobです。

4.1 分散ファイルシステム(今回はMinIoを使用)

ファイルシステムとは何ですか?

ファイル システムは、ファイルの管理と保存を担当するシステム ソフトウェアです。オペレーティング システムは、ファイル システムが提供するインターフェイスを通じてファイルにアクセスします。ユーザーは、オペレーティング システムを通じてディスク上のファイルにアクセスします。一般的なファイル システム: FAT16/FAT32、NTFS、HFS 、UFS、APFS、XFS、Ext4など。
ここに画像の説明を挿入
また、大容量のファイルを保存したい場合、どのように保存すればよいでしょうか?

分散ファイル システムは、大規模なユーザーが大規模なファイルにアクセスするためのソリューションです。
分散ファイル システムとは、大量のファイルを保存できないコンピュータのことです。ネットワークを介して複数のコンピュータを組織し、多数のファイルを共同で保存し、多数のユーザーからの要求を受け取ります。これらのコンピュータはネットワークを介して通信します。
ここに画像の説明を挿入
分散処理であるため、次のようなメリットがあります。
1. 1 台のコンピュータのファイル システム処理能力が拡張され、複数のコンピュータで同時に処理できるようになります。
2. 1 台のコンピュータがクラッシュした場合、コンピュータの別のコピーがデータを提供します。
3. 各コンピュータを異なる領域に配置できるため、ユーザーは近くのコンピュータにアクセスでき、アクセス速度が向上します。

4.2 分散タスクスケジューリングシステム(今回はXXL-JOBを使用)

ビデオのトランスコーディングはタスクの実行として理解できます。ビデオの数が比較的多い場合 (大量のタスク)、タスクのバッチを効率的に処理するにはどうすればよいでしょうか?
1. マルチスレッド
マルチスレッドとは、単一マシンのリソースを最大限に活用することです。
2. 分散マルチスレッドは
複数のコンピュータを駆使し、各コンピュータがマルチスレッド処理を行います。
オプション 2 はよりスケーラブルです。
ソリューション 2 は、分散タスク スケジューリング ソリューションです。

タスクのスケジュール設定とは何ですか?

タスクスケジューリングとは、その名のとおりタスクのスケジューリングであり、特定の業務を完了するために、システムが指定された時点、指定された時間間隔、または指定された実行回数に基づいてタスクを自動的に実行することを意味します。

分散タスクスケジューリングとは何ですか?

通常、タスクスケジューリングプログラムはアプリケーションに統合されています。たとえば、クーポンサービスには定期的にクーポンを発行するためのスケジューラが含まれ、決済サービスには定期的にレポートを生成するためのタスクスケジューラが含まれています。分散アーキテクチャにより、サービスは複数の冗長インスタンスを使用できます。タスク スケジューリングは、この分散システム環境で実行され、これを分散タスク スケジューリングと呼びます。
ここに画像の説明を挿入

アドバンテージ:

分散スケジューリングタスクの使用方法は、分散方式でタスクスケジューラを構築することと同等であり、分散システムの特性を備え、タスクのスケジューリングおよび処理能力を向上させることができます。 1. 並列タスクスケジューリング 並列タスクスケジューリングが実現され
ます
。スケジュールする必要のあるタスクが多数ある場合、コンピュータの CPU の処理能力には限界があるため、マルチスレッドのみに依存するとボトルネックが発生します。
タスク スケジューラが分散方式で展開されている場合は、各ノードをクラスタとして展開することもできるため、複数のコンピュータが共同してタスク スケジューリングを完了できます。タスクをいくつかのフラグメントに分割し、異なるインスタンスで並行して実行できます。タスクスケジューリングの処理効率。
2. 高可用性:
1 つのインスタンスがダウンしても、他のインスタンスのタスクの実行には影響しません。
3. 柔軟な拡張性:
クラスタにインスタンスを追加すると、タスク実行の処理効率を向上させることができます。
4. タスクの管理と監視
システム内に存在するすべてのスケジュールされたタスクを一元管理および監視します。開発者や運用保守担当者がタスクの実行状況を常に把握できるため、緊急時の対応が迅速に行えます。
5. タスクの繰り返し実行を避ける
クラスタモードでタスクスケジューリングを導入している場合、例えば上記の電子商取引システムにおいて、ある時点でクーポンを発行する例では、同じタスクスケジューリングが複数回実行される可能性があります。これは企業にとって多大な損失となるため、実行中の複数のインスタンスで同じタスクが 1 回だけ実行されるように制御する必要があります。

5.みにおの集中講義

5.1MinIoの紹介

MinIO は、他のアプリケーションと簡単に組み合わせて使用​​できる非常に軽量なサービスで、Amazon S3 クラウド ストレージ サービス インターフェイスと互換性があり、写真、ビデオ、ログ ファイル、データなどの大容量の非構造化データの保存に非常に適しています。バックアップ、データおよびコンテナ/仮想マシンのイメージなど。
主な特徴の 1 つは、軽量、使いやすく、強力で、さまざまなプラットフォームをサポートし、単一ファイルの最大 5TB を持ち、Amazon S3 インターフェイスと互換性があり、Java、Python、じゃ、行け。
公式ウェブサイト:https://min.io
中国語:https://www.minio.org.cn/、http://docs.minio.org.cn/docs/

MinIO クラスターは分散共有アーキテクチャを採用しており、各ノードはピアツーピア関係にあり、Nginx を通じて MinIO への負荷分散されたアクセスを実現できます。

分散化の利点は何ですか?

ビッグデータの分野では、通常の設計概念は中心がなく分散されています。Minio 分散モードは、可用性の高いオブジェクト ストレージ サービスの構築に役立ち、これらのストレージ デバイスを実際の物理的な場所に関係なく使用できます。異なるサーバーに分散された複数のハードディスクを組み合わせて、オブジェクト ストレージ サービスを形成しますハードディスクは異なるノードに分散されているため、分散型 Minio は単一障害点を回避します。
ここに画像の説明を挿入
Minio は、消去コード技術を使用してデータを保護します。これは、失われたデータや破損したデータを回復するための数学的アルゴリズムです。各ノードのディスクにデータ ブロックを冗長的に保存します。使用可能なすべてのディスクがセットを形成します。この図は 8 台のハード ドライブで構成されます。ファイルがアップロードされると、ファイルは消去符号アルゴリズムの計算によりブロックに保存されます ファイル自体を 4 つのデータ ブロックに分割することに加えて、4 つのチェック ブロックが生成され、データ ブロックとパリティ ブロックがこれらに分散して保存されますハードディスクは8台。
イレイジャーコーディングを使用する利点は、ハードドライブの数の半分 (N/2) が失われた場合でも、データを回復できることですたとえば、上記の収集で破損したハードディスクが 4 台未満の場合は、アップロードとダウンロードに影響を与えることなくデータの復旧が保証されますが、半分以上のハードディスクが破損した場合は復旧できません。

5.2最小Ioの使用

インストールしてログインするとこのような画面になりますが、非常にシンプルなインターフェースなので使い方を自分で覚えることができます。

ここに画像の説明を挿入
バケット、バケット、ファイルが保存されているディレクトリに相当し、複数のバケットを作成できます

5.2.1maven の依存関係は次のとおりです。

最小要件 Java 1.8 以降:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>

5.2.2 設定

構成ファイル内の独自の minIo 情報に従って構成します
ここに画像の説明を挿入
。上記のバケット名は、以下の私のバケット名に対応します。
ここに画像の説明を挿入

minio サービスに接続するには 3 つのパラメータが必要です。
エンドポイントオブジェクト ストレージ サービスの URL
アクセス キー。アクセス キーはユーザー ID のようなもので、アカウントを一意に識別できます。
秘密キー 秘密キーはアカウントのパスワードです。

minio:
  endpoint: http://192.168.101.65:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket:
    files: mediafiles
    videofiles: video

テスト用でない場合は、次のような簡単に使用できるMinIo 構成クラスを作成できます。

package com.xuecheng.media.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description minio配置类
 */
@Configuration
public class MinioConfig {
    
    

    @Value("${minio.endpoint}")
    private String endpoint;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;

    @Bean
    public MinioClient minioClient() {
    
    

        MinioClient minioClient =
                MinioClient.builder()
                        .endpoint("http://192.168.101.65:9000")
                        .credentials(accessKey, secretKey)
                        .build();
        return minioClient;
    }

}

5.2.3 ブレークポイント再開テクノロジー (ビデオのアップロードについて知っておく必要があります)

ブレークポイント再開アップロードとは何ですか
? 通常、ビデオ ファイルは比較的大きいため、メディア アセット システムのファイル アップロード要件は、大きなファイルのアップロード要件を満たす必要があります。httpプロトコル自体にはアップロードするファイルのサイズに制限はありませんが、お客様のネットワーク環境やコンピュータのハードウェア環境の品質にばらつきがあり、大きなファイルをアップロードしようとしているときにネットワークが切断され、アップロードが完了しない場合があります。 、顧客は再度アップロードする必要があります。ユーザー エクスペリエンスが非常に悪いため、大きなファイルをアップロードするための最も基本的な要件は、休憩後にアップロードを再開することです。
再開可能なアップロードとは:
Baidu 百科事典からの引用: 再開可能なアップロードとは、ダウンロードまたはアップロード時に、ダウンロードまたはアップロード タスク (ファイルまたは圧縮パッケージ) を人為的にいくつかの部分に分割することを指し、各部分はスレッドのアップロードまたはダウンロードを採用します。ネットワーク障害が発生した場合でも、アップロードまたはダウンロードされた部分から未完成の部分をアップロードおよびダウンロードし続けることができます。最初からアップロードおよびダウンロードする必要はありません。ブレークポイント再開により、操作時間を節約し、ユーザー エクスペリエンスを向上させることができます。
ブレークポイントからアップロードを再開するプロセスを次の図に示します。
ここに画像の説明を挿入

プロセスは次のとおりです:
1. フロントエンドでアップロードする前にファイルをチャンクに分割します
2. 一度に 1 つのチャンクをアップロードします アップロードが中断された後に再アップロードします アップロードされたチャンクを再度アップロードする必要はありません
3 . 各チャンクがアップロードされた後、サーバー上のファイルをマージします。

5.2.4minIo サンプル テスト コード

これは基本的な使用法を示すだけなので、独自の用途に合わせて変更する必要があります。


import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MinioTest {
    
    


    MinioClient minioClient =
            MinioClient.builder()
                    .endpoint("http://192.168.101.65:9000")
                    .credentials("minioadmin", "minioadmin")
                    .build();

    @Test
    public void test_upload() throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    
    

        //根据扩展名取出媒体资源类型mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流

        if (extensionMatch != null) {
    
    
            String mimeType1 = extensionMatch.getMimeType();

        }

        //上传文件的参数信息
        UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                .bucket("testbuket")//桶
                .filename("C:\\Users\\a2262\\Pictures\\blog\\Blue Whale.png")//指定本地文件路径
                // .object("blog.png")//对象名,文件存放路径相对于桶名
                .object("test/01/blog.png") // 在testbuket/test/01下存放文件,命名为blog.png
                // .contentType("image/png") //设置媒体文件类型
                .contentType(mimeType)
                .build();

        //上传文件
        minioClient.uploadObject(uploadObjectArgs);


    }

    @Test
    public void test_delete() throws Exception {
    
    

        //删除文件的参数信息
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket("testbuket")
                .object("blog.png")
                .build();

        //删除文件
        minioClient.removeObject(removeObjectArgs);

    }

    //查询文件,从minio下载
    @Test
    public void getFile() throws IOException {
    
    
        GetObjectArgs getObjectArgs = GetObjectArgs
                .builder()
                .bucket("testbuket")
                .object("test/01/blog.png")
                .build();
        try (
                //这是远程流,不稳定
                FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
                FileOutputStream outputStream = new FileOutputStream(new File("D:\\MinIo\\blog.png"));
        ) {
    
    
            IOUtils.copy(inputStream, outputStream);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        //校验文件的完整性对文件的内容进行md5
        FileInputStream fileInputStream1
                = new FileInputStream(new File("C:\\Users\\a2262\\Pictures\\blog\\Blue Whale.png"));
        String source_md5 = DigestUtils.md5Hex(fileInputStream1);
        FileInputStream fileInputStream
                = new FileInputStream(new File("D:\\MinIo\\blog.png"));
        String local_md5 = DigestUtils.md5Hex(fileInputStream);
        if (source_md5.equals(local_md5)) {
    
    
            System.out.println("下载成功");

        }
    }

    //将分块文件上传到minio
    @Test
    public void uploadChunk() throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    
    
        for (int i = 0; i < 2; i++) {
    
    
            //上传文件的参数信息
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket("testbuket")//桶
                    .filename("D:\\MinIo\\upload\\chunk\\" + i)//指定本地文件路径
                    .object("chunk/" + i)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            System.out.println("上传分块" + i + "成功");
        }
    }


    //调用minio接口合并分块
    @Test
    public void uploadMerge() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    
    
        //指定分块文件信息
        List<ComposeSource> source = Stream.iterate(0, i -> ++i).limit(2).map(
                        i -> ComposeSource
                                .builder()
                                .bucket("testbuket")
                                .object("chunk/" + i)
                                .build()
                )
                .collect(Collectors.toList());

        //指定分块后文件信息
        ComposeObjectArgs testbuket = ComposeObjectArgs.builder()
                .bucket("testbuket")
                .object("merge01.mp4")
                .sources(source)
                .build();
        //合并文件
        minioClient.composeObject(testbuket);

    }


    //批量清理分块

    public void test_removeObjects() {
    
    
        //合并分块完成将分块文件清除
        List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                .limit(2)
                .map(i -> new DeleteObject("chunk/".concat(Integer.toString(i))))
                .collect(Collectors.toList());

        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder()
                        .bucket("testbucket")
                        .objects(deleteObjects)
                        .build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        results.forEach(r -> {
    
    
            DeleteError deleteError = null;
            try {
    
    
                deleteError = r.get();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        });
    }

    //分块测试
    @Test
    public void testChunk() throws IOException {
    
    

        //源文件
        File sourceFile = new File("C:\\Users\\a2262\\Videos\\视频\\3db161-170d56d7335.mp4");
        //存储文件路径
        String chunkFilePath="D:\\MinIo\\upload\\chunk\\";
        File mk_file = new File(chunkFilePath);
        if(!mk_file.exists()){
    
    
            mk_file.mkdirs();
        }
        //分块大小,默认最小5mb,否则要改源码
        int chunkSize=1024*1024*5;
        //文件分块数,向上取整
        int chunkNum= (int) Math.ceil(sourceFile.length()*1.0/chunkSize);
        //用流读数据
        RandomAccessFile r = new RandomAccessFile(sourceFile, "r");
        //缓存区
        byte[] bytes = new byte[1024];
        for (int i = 0; i < chunkNum; i++) {
    
    
            File chunkFile = new File(chunkFilePath + i);
            if(chunkFile.exists()){
    
    
                chunkFile.delete();
            }
            RandomAccessFile rw = new RandomAccessFile(chunkFile, "rw");
            int len=-1;
            while((len=r.read(bytes))!=-1){
    
    
                rw.write(bytes,0,len);
                if(chunkFile.length()>=chunkSize){
    
    
                    break;
                }
            }
            rw.close();
        }
        r.close();

    }

    //将分块合并
    @Test
    public void testMerge() throws IOException {
    
    
        //块文件目录
        File chunkFolder=new File("D:\\MinIo\\upload\\chunk");
        //源文件
        File sourceFile = new File("C:\\Users\\a2262\\Videos\\视频\\3db161-170d56d7335.mp4");
        File mk_file = new File("D:\\MinIo\\merge\\chunk");
        if(!mk_file.exists()){
    
    
            mk_file.mkdirs();
        }
        //合并后的文件
        File mergeFile = new File("D:\\MinIo\\merge\\chunk\\3db161-170d56d7335.mp4");
        //取出所有的分块文件
        File[] files = chunkFolder.listFiles();
        //将数组转化为list
        List<File> filesList = Arrays.asList(files);

        Collections.sort(filesList, new Comparator<File>() {
    
    
            @Override
            public int compare(File o1, File o2) {
    
    
                return Integer.parseInt(o1.getName())-Integer.parseInt(o2.getName());
            }
        });

        //向合并文件写的流
        RandomAccessFile rw = new RandomAccessFile(mergeFile, "rw");
        //缓冲区
        byte[] bytes =new byte[1024];

        for (File file:filesList){
    
    
            //读分块的流
            RandomAccessFile r = new RandomAccessFile(file, "r");
            int len=-1;
            while((len=r.read(bytes))!=-1){
    
    
                rw.write(bytes,0,len);
            }
            r.close();
        }
        rw.close();

        //合并文件完成进行md5校验
        FileInputStream merge_f = new FileInputStream(mergeFile);
        FileInputStream chunk_f = new FileInputStream(sourceFile);
        String s1 = DigestUtils.md5Hex(merge_f);
        String s2 = DigestUtils.md5Hex(chunk_f);
        if(s1.equals(s2)){
    
    
            System.out.println("合并完成");
        }
    }


}

6.XXL-JOB集中講義

6.1 はじめに

XXL-JOB は軽量の分散タスク スケジューリング プラットフォームであり、その中心的な設計目標は、迅速な開発、簡単な学習、軽量、簡単な拡張です。ソースコードは現在公開されており、多くの企業のオンライン製品ラインに接続されており、すぐに使用できるようになっています。
公式 Web サイト: https://www.xuxueli.com/xxl-job/
XXL-JOB には、主にスケジューリング センター、実行者、およびタスクが含まれます。
ここに画像の説明を挿入
スケジューリング センター:
スケジューリング情報の管理、スケジューリング構成に従ってスケジューリング要求の発行を担当します。ビジネスコードそのもの;
主な責任は実行者管理、タスク管理、運用と保守の監視、ログ管理などです
タスク実行者:
スケジューリング要求の受信とタスクロジックの実行を担当します;
責任が登録サービスとタスク実行サービスである限り(タスク受信後、スレッドプールのタスクキューに投入されます)、実行結果レポート、ログサービスなど。
タスク:特定の業務処理の実行を担当します。

ディスパッチ センターと実行者間のワークフローは次のとおりです。
ここに画像の説明を挿入
実行プロセス:
1. タスク実行者は、設定されたディスパッチ センター アドレスに従ってディスパッチ センターに自動的に登録されます
。 2. タスクのトリガー条件が満たされると、ディスパッチ センターはタスクを発行します。
3. 実行プログラムは、スレッド プールがタスクを実行し、実行結果をメモリ キューに入れ、実行ログをログ ファイルに書き込みます。 4. 実行プログラムは、メモリ キュー内の実行結果を消費し、それらをアクティブにレポートします

5. ユーザーがディスパッチ センターでタスク ログを表示すると、ディスパッチ センターはタスク実行プログラムを要求し、タスク実行プログラムはタスク ログ ファイルを読み取ってログの詳細を返します

6.2 使用方法

6.2.1 最初のXXL-JOBのダウンロード

GitHub:https://github.com/xuxueli/xxl-jobб
云:https://gitee.com/xuxueli0323/xxl-job

ダウンロードして解凍した後、IDEA を使用して解凍されたディレクトリを開きます。
ここに画像の説明を挿入
xxl-job-admin: scheduling center
xxl-job-core: public dependency
xxl-job-executor-samples: Executor Sample (適切なバージョンの Executor を選択します。直接実行できます)使用)
: xxl-job-executor-sample-springboot: Springboot バージョン、Springboot を通じてエグゼキュータを管理、この方法が推奨されます;
:xxl-job-executor-sample-frameless: フレームレス バージョン;
doc: ドキュメント、データベース スクリプトを含む

6.2.2 ドキュメントでデータベース スクリプトを作成する


作成後は下図のようになります

ここに画像の説明を挿入

6.2.3 XXL-JOB へのアクセス

http://あなたのIPアドレス:8088/xxl-job-admin/

インターフェースはこんな感じここに画像の説明を挿入

6.3 アクチュエータ

エグゼキュータは、ディスパッチ センターと通信し、ディスパッチ センターによって開始されたタスク スケジューリング要求を受信する責任があります。エグゼキュータ
の構成:
ここに画像の説明を挿入
[追加] をクリックして、エグゼキュータ情報を入力します。Appname は、xxl 情報を構成するときに指定したエグゼキュータのアプリケーション名です。ナコス。

保存
ここに画像の説明を挿入

6.4 構成

Maven の依存関係

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.1</version>
</dependency>

設定ファイル

xxl:
  job:
    admin: 
      addresses: http://你的ip地址:8088/xxl-job-admin 
    executor:
      appname: media-process-service #执行器名字
      address: 
      ip: 
      port: 9999
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    accessToken: default_token

構成内の appname はエグゼキュータのアプリケーション名であり、ポートはエグゼキュータが起動されるポートであることに注意してください。複数のエグゼキュータがローカルで起動される場合、ポートを繰り返すことはできないことに注意してください。

構成クラス

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * xxl-job config
 */
@Configuration
public class XxlJobConfig {
    
    
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
    
    
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

    /**
     * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
     *
     *      1、引入依赖:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置文件,或者容器启动变量
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、获取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */


}

プロジェクトを再起動した後、XXL-Job インターフェイスを確認すると、
ここに画像の説明を挿入オンライン マシン アドレスに 1 つの実行者が表示されます。または、起動後のログを観察し、以下のようなログが表示されていれば、
ここに画像の説明を挿入
実行者はディスパッチセンターに正常に登録されています。

6.5 タスクの実行

以下のようなサンプルコードを参照してください
ここに画像の説明を挿入

Java
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @description 测试执行器
 * @author Mr.M
 * @date 2022/9/13 20:32
 * @version 1.0
 */
 @Component
 @Slf4j
public class SampleJob {
    
    

 /**
  * 1、简单任务示例(Bean模式)
  */
 @XxlJob("testJob")
 public void testJob() throws Exception {
    
    
  log.info("开始执行.....");

 }

}

次に、ディスパッチ センターにタスクを追加し、 タスク管理 に入ります
ここに画像の説明を挿入
。 [追加] をクリックして、タスク情報を入力します。
ここに画像の説明を挿入
スケジュール タイプ:
固定速度は、一定の間隔でスケジュールされたスケジュールを指します。
Cron は、Cron 式を通じて、より豊富なタイミング スケジューリング戦略を実装します。
Cron 式は、スケジュール ポリシーを定義できる文字列です。形式は次のとおりです:
{秒} {分} {時間} {日付} {月} {週} {年 (空でも可)}
xxl-job以下を構成するためのグラフィックス インターフェイスを提供します。

ここに画像の説明を挿入
例は次のとおりです:
30 10 1 * * ? 毎日 1:10:30 にトリガーします 0/30
* * * * ? 30 秒ごとにトリガーします

  • 0/10 * * * ? 10 分ごとにトリガー

動作モード:
BEAN と GLUE があり、Bean モードはプロジェクトの実行者のタスク コードを記述するのによく使用され、GLUE はディスパッチ センターにタスク コードを記述します。

JobHandler :
タスク メソッドの名前。タスク メソッドの上の @XxlJob アノテーションに名前を入力します。
ここに画像の説明を挿入

ルーティング戦略:
エグゼキュータ クラスタがデプロイされるとき、ディスパッチ センターがどのエグゼキュータにタスクを配信するか。ここで最初のものを選択すると、タスクは最初のエグゼキュータにのみ送信されます。ルーティング ストラテジの他のオプションについては、フラグメント ブロードキャスト セクションで詳しく説明します。後で説明します。
高度な設定のその他の設定項目については、後のフラグメント ブロードキャストの章で詳細に説明します。

正常に追加され、タスクが開始されました

タスクを停止するには、スケジュール内の操作を実行する必要があります。
ここに画像の説明を挿入
また、ログのクリアも実行できます。
ここに画像の説明を挿入

6.6 断片化されたブロードキャストのルーティング戦略

6.6.1 ルーティング戦略とは何ですか?

高度な構成:
- ルーティング戦略: エグゼキューター クラスターが展開されると、
FIRST (最初): 最初のマシンを固定的に選択、
LAST (最後): 最後のマシンを固定的に選択、
ROUND (ポーリング): などの豊富なルーティング戦略が提供されます。
RANDOM (ランダム): オンライン マシンをランダムに選択します。
CONSISTENT_HASH (一貫性 HASH): 各タスクはハッシュ アルゴリズムに従って特定のマシンを選択し、すべてのタスクは異なるマシン上で均等にハッシュされます。
LEAST_FREQUENTLY_USED (使用頻度が最も低い): 使用頻度が最も低いマシンが最初に選択されます;
LEAST_RECENTLY_USED (最近使用されていないマシン): 最も長い間使用されていないマシンが最初に選択され
ます。ハートビート検出に成功したマシンがターゲット エグゼキュータとして選択され、スケジューリングを開始します。
BUSYOVER (ビジー転送): アイドル検出が順番に実行され、アイドル検出に成功した最初のマシンがターゲット エグゼキュータとして選択され、スケジューリングを開始します。 SHARDING_BROADCAST (シャー
ディングブロードキャスト) : ブロードキャストは、対応するクラスター内のすべてのマシンをトリガーしてタスクを実行し、システムは自動的にフラグメンテーション パラメーターを送信します。フラグメンテーション タスクはフラグメンテーション パラメーターに従って開発できます。

- 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度,通过子任务可以实现一个任务执行完成去执行另一个任务。
- 调度过期策略:
    - 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
    - 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
    单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
    丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
    覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;

6.6.2 細分化されたブロードキャスト戦略に焦点を当てる

焦点はシャーディング ブロードキャスト戦略です。シャーディングとは、スケジューリング センターがエグゼキュータをディメンションとしてスライスし、クラスタ内のエグゼキュータにシリアル番号 0、1、2、3... でマークを付けることを意味し、ブロードキャストは各スケジューリングを指します。タスクのスケジューリングはクラスター内のすべての実行者に送信され、リクエストにはシャーディング パラメーターが含まれておりシャーディング パラメーターはシャーディング ビジネス処理のために取得されます

各エグゼキュータはスケジューリング リクエストを受信し、シャーディング パラメータも受信します。
xxl-job は、シャード数を動的に増やすためのエグゼキュータ クラスタの動的な拡張をサポートしています。ワークロードが増加すると、より多くのエグゼキュータをクラスタにデプロイでき、ディスパッチ センターはシャードの数を動的に変更します。

ジョブ シャーディングはどのようなシナリオに適していますか?
• シャーディング タスク シナリオ: 10 台のエグゼキュータからなるクラスタは 100,000 個のデータを処理します。各マシンは 10,000 個のデータのみを処理するだけで済み、消費時間が 10 分の 1 に削減されます。 • ブロードキャスト タスク シナリオ: ブロードキャスト エグゼキュータはシェル スクリプトを実行し、ブロードキャストします。
同時にクラスタノードのキャッシュ更新などを実行します
したがって、ブロードキャストシャーディング方式は、各エグゼキュータの能力を最大限に発揮するだけでなく、シャーディングパラメータに従ってタスクを実行するかどうかを制御し、最終的にエグゼキュータクラスタの分散処理タスクを柔軟に制御することができます。

6.6.3 フラグメントブロードキャストの使用例

コード

Java
/**
  * 2、分片广播任务
  */
 @XxlJob("shardingJobHandler")
 public void shardingJobHandler() throws Exception {
    
    

  // 分片参数
  int shardIndex = XxlJobHelper.getShardIndex();
  int shardTotal = XxlJobHelper.getShardTotal();

log.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
log.info("开始执行第"+shardIndex+"批任务");

 }

ディスパッチ センターにタスクを追加します。
ここに画像の説明を挿入
正常に追加され、タスクが開始されます。
ここに画像の説明を挿入
ログを確認してください。
ここに画像の説明を挿入

6.7 複数の実行者が重複したタスクをクエリしないようにするにはどうすればよいですか?

各エグゼキュータは、フラグメントの総数とフラグメント シーケンス番号という 2 つのパラメータを持つブロードキャスト タスクを受け取ります。各タスクがデータ テーブルからフェッチされるとき、タスク ID はシャードの総数を法として指定でき、それがシャードのシリアル番号と等しい場合、タスクは実行されます。
上記の 2 つのエグゼキューター インスタンスの場合、シャードの合計数は 2 で、シーケンス番号は次のようにタスク 1 から始まる 0 と 1 です。 1
% 2 = 1 エグゼキューター 2 は
2 % 2 = 0 エグゼキューター 1 は
3 %を実行します。 2 = 1 が実行され、サーバー 2 が実行されます

6.8 タスクが繰り返し実行されないようにする方法

ジョブ シャーディング スキームにより、実行者は繰り返しのないタスクをクエリできるようになります。実行者がビデオの処理を完了していない場合、スケジューリング センターはこの時点で再度スケジュールを要求します。同じビデオを繰り返し処理しないようにするには、どうすればよいですか?

最初にスケジュール有効期限ポリシーを設定します:
次のようにドキュメントを表示します
: -スケジュール有効期限ポリシー: 無視する、即時補償を 1 回トリガーするなどを含む、スケジュール センターのスケジューリング時間に失敗した場合の補償処理戦略。 -Ignore : スケジュールの有効期限が切れた後、無視し
ます。期限切れのタスクを削除し、現在時刻から開始します 次のトリガー時刻の再計算を開始します; -すぐに 1 回実行: スケジュールの期限が切れたら、すぐに 1 回実行し、現在時刻から次のトリガー時刻を再計算します; -ブロッキング処理戦略: 処理戦略スケジュールが密集しすぎて実行者が処理する時間がない場合;ここでは、一度実行されるとすぐに同じタスクが繰り返し実行される可能性があるため、無視することを選択します。



ここに画像の説明を挿入

これらの構成だけでタスクが繰り返し実行されないようにすることができますか?

これが不可能な場合でも、タスク処理の冪等性を確保する必要があります。

タスクのべき等性とは何ですか?

タスクの冪等性は、データが何回操作されても、操作の結果は常に同じであることを意味します。このプロジェクトで達成すべきことは、タスクが何度スケジュールされても、同じビデオが正常にトランスコードされるのは 1 回だけであるということです。

冪等性とは何ですか?

これは、リソースに対する 1 つまたは複数のリクエストがリソース自体に対して同じ結果をもたらす必要があることを説明しています。
冪等性とは、悪意のあるブラッシングや繰り返しの支払いなど、繰り返し送信される問題を解決することです。

冪等性を解決するために一般的に使用されるソリューション:

1)データベース制約(一意のインデックス、主キーなど)。
2)データベースでは楽観ロックがよく使われており、データを更新する際には楽観ロックの状態に応じて更新されます。
3)固有のシリアル番号: オペレーションは固有のシリアル番号を渡し、シリアル番号と等しいと判断された場合にオペレーションが実行されます。

上記の方法は分散ロックの使用に属します

分散ロックとは何ですか?

これは、同期された同期ロックの方法とは似ていませんが、同期では、同じ仮想マシン内の複数のスレッドがロックを競合することのみを保証できます。仮想マシンはすべて同じロックを取得します。ロックは、ロックおよびロック解除のサービスを提供する別個のプログラムです。ロックは特定の仮想マシンに属さず、分散して展開され、複数の仮想マシンで共有されます。このロックを分散ロックと呼びます。
ここに画像の説明を挿入

7. FFmpegの使用を補足する

ダウンロード: FFmpeg https://www.ffmpeg.org/download.html#build-windows

共通ツールソフトウェアディレクトリからffmpeg.exeを検索し、環境変数のパスにffmpeg.exeを追加してください。
正常かどうかをテストします: cmd は ffmpeg -version を実行します

7.1 サンプルコード

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Mp4VideoUtil extends VideoUtil {
    
    

    String ffmpeg_path = "D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";//ffmpeg的安装位置
    String video_path = "D:\\BaiduNetdiskDownload\\test1.avi";
    String mp4_name = "test1.mp4";
    String mp4folder_path = "D:/BaiduNetdiskDownload/Movies/test1/";
    public Mp4VideoUtil(String ffmpeg_path, String video_path, String mp4_name, String mp4folder_path){
    
    
        super(ffmpeg_path);
        this.ffmpeg_path = ffmpeg_path;
        this.video_path = video_path;
        this.mp4_name = mp4_name;
        this.mp4folder_path = mp4folder_path;
    }
    //清除已生成的mp4
    private void clear_mp4(String mp4_path){
    
    
        //删除原来已经生成的m3u8及ts文件
        File mp4File = new File(mp4_path);
        if(mp4File.exists() && mp4File.isFile()){
    
    
            mp4File.delete();
        }
    }
    /**
     * 视频编码,生成mp4文件
     * @return 成功返回success,失败返回控制台日志
     */
    public String generateMp4(){
    
    
        //清除已生成的mp4
//        clear_mp4(mp4folder_path+mp4_name);
        clear_mp4(mp4folder_path);
        /*
        ffmpeg.exe -i  lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4
         */
        List<String> commend = new ArrayList<String>();
        //commend.add("D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe");
        commend.add(ffmpeg_path);
        commend.add("-i");
//        commend.add("D:\\BaiduNetdiskDownload\\test1.avi");
        commend.add(video_path);
        commend.add("-c:v");
        commend.add("libx264");
        commend.add("-y");//覆盖输出文件
        commend.add("-s");
        commend.add("1280x720");
        commend.add("-pix_fmt");
        commend.add("yuv420p");
        commend.add("-b:a");
        commend.add("63k");
        commend.add("-b:v");
        commend.add("753k");
        commend.add("-r");
        commend.add("18");
//        commend.add(mp4folder_path  + mp4_name );
        commend.add(mp4folder_path  );
        String outstring = null;
        try {
    
    
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            //将标准输入流和错误输入流合并,通过标准输入流程读取信息
            builder.redirectErrorStream(true);
            Process p = builder.start();
            outstring = waitFor(p);

        } catch (Exception ex) {
    
    

            ex.printStackTrace();

        }
//        Boolean check_video_time = this.check_video_time(video_path, mp4folder_path + mp4_name);
        Boolean check_video_time = this.check_video_time(video_path, mp4folder_path);
        if(!check_video_time){
    
    
            return outstring;
        }else{
    
    
            return "success";
        }
    }

    public static void main(String[] args) throws IOException {
    
    
        //ffmpeg的路径
        String ffmpeg_path = "D:\\Baidu Netdisk\\item1\\day1\\常用软件\\ffmpeg\\ffmpeg.exe";//ffmpeg的安装位置
        //源avi视频的路径
        String video_path = "D:\\develop\\bigfile_test\\nacos01.avi";
        //转换后mp4文件的名称
        String mp4_name = "nacos01.mp4";
        //转换后mp4文件的路径
        String mp4_path = "D:\\MinIo\\nacos01.mp4";
        //创建工具类对象
        Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
        //开始视频转换,成功将返回success
        String s = videoUtil.generateMp4();
        System.out.println(s);
    }
}

おすすめ

転載: blog.csdn.net/m0_71106830/article/details/131794583