牛革!Redis 6.0はどのようにして大幅なパフォーマンスの向上を実現しますか?

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

前書き:

Redisは100k以上のQPSを簡単にサポートでき、Reactorモデルに基づくI / Oマルチプレックス、メモリ内操作、および静的消費を回避するためのコマンドのシングルスレッド実行と切り離せません。パフォーマンスはほとんどのアプリケーションシナリオに対応できますが、反復で最適化を継続し、マルチコア時代にマルチスレッドの利点を使用する方法も、すべての人の注目を集めています。パフォーマンスの最適化は、システムリソースレベルでI / OとCPUから開始できることがわかっています。Redisの場合、その機能はCPUの計算能力にあまり依存しません。つまり、CPUを集中的に使用するアプリケーションではなく、メモリ内の操作もバイパスされます。通常、パフォーマンスの遅いディスクI / Oは遅延するため、Redis 6.0バージョンでは、ネットワークI / Oから始めて、読み取りと書き込みを支援するスレッドI / Oを導入し、シナリオによっては、パフォーマンスが大幅に向上しました。この記事では、Redisのイベントモデルを紹介し、スレッドI / Oがパフォーマンスの向上にどのように役立つか、およびその実装の原則を分析します。

前書き

Redisは、コマンドの実行前後のネットワークI / Oパフォーマンスを向上させるために、バージョン6.0からスレッドI / Oを導入しました。この記事では、Redisのメインプロセスの分析から始め、ネットワークI / Oが発生する場所、および既存のネットワークI / Oモデルについて説明し、次にスレッドI / Oの新しいモデル、実装、および効果的なシナリオを紹介し、最後にシナリオテストを実施します。 、スレッドI / Oをオフまたはオンにした場合のパフォーマンスの違いと、スレッドI / Oを有効にして単一インスタンスでクラスターを構築した場合のパフォーマンスの違いを比較します。Redisのサイクルプロセスをすでに理解している場合は 、スレッドI / Oの関連部分に直接スキップできます。新機能の実際の改善のみに関心がある場合は、パフォーマンステスト 部分にスキップし て確認できます。

Redisのしくみ

イベントループ

メイン

Redisの入り口はserver.cの下にあり、main()メソッドのフローが図に示されています。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

Redisがmain()メソッドで最初に行う必要があるのは、さまざまなライブラリとサービス構成を 初期化すること です。具体例:

  • crc64_init()は、crc検証用のルックアップテーブルを初期化します
  • getRandomBytes()は、ハッシュテーブルのシードとして使用される初期化値としてランダムな要素でハッシュシードを埋めます
  • ..。
  • サーバーオブジェクトのプロパティに対する多数の初期化操作は、initServerConfig()で実行されます。
    • 16e05f486b8d41e79593a35c8b96edaff101c194などのserver.runidを初期化します
    • 現在のタイムゾーン情報を取得し、server.timezoneに保存します
    • 接続されたクライアントのクライアントIDが1から増加するように、server.next_client_id値を初期化します。
    • ..。
  • ACLInit()は、Redis 6.0の新しいACLシステムの初期化操作であり、ユーザーリスト、ACLログ、デフォルトユーザーなどの初期化などの情報が含まれます。
  • moduleInitModulesSystem()およびtlsInit()を介してモジュールシステムとSSLを初期化します
  • ..。

初期化が 完了すると、ユーザーの起動パラメーターの読み取りが開始され ます。ほとんどの構成ロードプロセスと同様に、Redisは、文字列マッチングを通じてユーザーが入力したargcおよびargv []も分析します。このプロセスは次のように発生する可能性があります。

  • 構成ファイルのパスを取得し、server.configfileの値を変更してから、構成ファイルをロードします
  • loadmoduleや対応するモジュールファイルパスなどの起動オプションパラメータを取得し、options変数に保存します

パラメータを解析した後、loadServerConfig()を実行し 、構成ファイルを読み取り、コマンドラインパラメータオプションの内容とマージし て構成変数形成し、名前と値を1つずつ構成リストに設定します。構成ごとに、対応するスイッチケースコードがあります。たとえば、loadmoduleの場合、queueLoadModule()メソッドが実行され、実際の構成のロードが完了します。

...
        } else if (!strcasecmp(argv[0],"logfile") && argc == 2) {   
            ...         } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
            queueLoadModule(argv[1],&argv[2],argc-2);
        } else if (!strcasecmp(argv[0],"sentinel")) {
...

メインメソッドプロセスに戻ると、Redisは起動ログの印刷を開始し、initServer()メソッドを実行します。サービスは、構成項目に従ってサーバーオブジェクトのコンテンツを初期化し続け ます。次に例を 示します。

  • イベントループ構造aeEventLoop(ae.h​​で定義)を作成し、server.elに割り当てます。
  • 構成されたdbの数に応じて、sizeof(redisDb)* dbnumのメモリスペースを割り当て、server.dbはこのスペースのアドレスポインタを保存します
  • 各dbはredisDb構造であり、この構造でキーを保存し、有効期限を保存する辞書は、空のdictに初期化されます。
  • ..。

その後、さまざまな動作モードに応じていくつかの初期化が行われます。たとえば、通常モードの実行中は、通常のログを記録してディスクの永続データをロードしますが、センチネルモードでは、センチネルログを記録し、データをロードしません。

すべての準備操作が完了すると、RedisはaeMain()イベントループに陥り始めます。このループでは、Redisが終了するまで、発生するさまざまなイベントを処理するためにaeProcessEvents()が継続的に実行されます。

2つのイベント

Redisには、タイムイベント とファイルイベントの2種類のイベントが あり ます 。

タイムイベントは、特定の時間に発生するイベントであり、Redisにリンクリストとして記録されます。新しいタイムイベントが作成されるたびに、aeTimeEventノードがリンクリストの先頭に挿入され、イベントの場所が格納されます。発生した場合、処理するためにどのメソッドを呼び出す必要があります。リンクリスト全体をトラバースすることで、リンクリスト内のノードが自己増加IDの順序で配置され、発生時間の次元で順序が狂っているため、最新のタイムイベントが発生するまでの時間を知ることができます。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

ファイルイベントは、I / Oによって引き起こされたイベントと見なすことができます。クライアントがコマンドを送信すると、サーバーは読み取りイベントに対応する読み取りI / Oを生成します。また、クライアントがサーバーからのメッセージを待機している場合も、サービスを許可するには書き込み可能になる必要があります。最後にコンテンツを書き込むので、書き込みイベントに対応します。AE_READABLEイベントは、クライアントが接続を確立するとき、コマンドを送信するとき、または他の接続が読み取り可能になるときに発生し、AE_WRITABLEイベントは、クライアント接続が書き込み可能になるときに発生します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

ファイルイベントの構造ははるかに単純です。aeFileEventは、読み取り可能イベントか書き込み可能イベントか、対応する処理方法、およびユーザーデータを記録します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

2つのイベントが同時に発生した場合、RedisはAE_READABLEイベントを優先します。

aeProcessEvents

aeProcessEvents()メソッドは、発生した、または発生しようとしているさまざまなイベントを処理します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

aeMain()ループがaeProcessEvents()に入った後、Redisは最初に次のタイムイベントが発生するタイミングをチェックします。タイムイベントが発生する前の期間中に、多重化されたAPI aeApiPoll()を呼び出してブロックし、ファイルイベントの発生を待ちます。ファイルイベントが発生していない場合は、タイムアウト後に0が返されます。それ以外の場合は、numeventsが発生したファイルイベントの数が返されます。

処理するファイルイベントの場合、RedisはAE_READABLEイベントのrfileProcメソッドとAE_WRITABLEイベントのwfileProcメソッドを呼び出して処理します。

...
            if (!invert && fe->mask & mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);                fired++;                fe = &eventLoop->events[fd];            }            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);                    fired++;                }            }...

前の処理が完了した後、Redisは引き続きprocessTimeEvents()を呼び出してタイムイベントを処理します。リンクされたタイムイベントのリスト全体をトラバースします。一定期間が経過し(待機のブロックまたはファイルイベントの処理に時間がかかる)、タイムイベントが発生した場合は、対応するタイムイベントのtimeProcメソッドを呼び出して、経過したすべてのタイムイベントを処理します。

...
        if (te->when <= now) {
            ...            retval = te->timeProc(eventLoop, id, te->clientData);            ...            processed++;            ...        }...

ファイルイベントの実行後に最新のタイムイベントに到達しなかった場合、このaeMain()ループではタイムイベントは実行されず、次のループに入ります。

コマンドの実行前後に何が起こったのか

クライアントがRedisに接続するときに、connSetReadHandler(conn、readQueryFromClient)を実行することにより、読み取りイベントが発生すると、readQueryFromClient()が読み取りイベントのハンドラーとして使用されます。

クライアントからコマンド要求を受信すると、Redisはいくつかのチェックと統計を実行してから、read()メソッドを呼び出して、接続内のデータをclient.querybufメッセージバッファーに読み込みます。

void readQueryFromClient(connection *conn) {
    ...    nread = connRead(c->conn, c->querybuf+qblen, readlen);    ...static inline int connRead(connection *conn, void *buf, size_t buf_len) {
    return conn->type->read(conn, buf, buf_len);
}static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
    int ret = read(conn->fd, buf, buf_len);
    ...}

次に、processInputBuffer(c)を入力して入力バッファー内のメッセージの読み取りを開始し、最後にprocessCommand(c)を入力して入力コマンドの処理を開始します。

コマンドが実行された後、結果は最初にclient.bufに保存され、addReply(client * c、robj * obj)メソッドが呼び出されて、クライアントオブジェクトがserver.clients_pending_writeリストに追加されます。この時点で、現在のコマンド、つまりAE_READABLEイベントは基本的に処理されています。いくつかの追加の統計データと後処理を除いて、これ以上の応答メッセージは送信されません。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

現在のaeProcessEvents()メソッドが終了したら、次のサイクルに入り  ます。2番目のサイクルはI / O多重化インターフェイスを呼び出して、ファイルイベントが発生するのを待ちます。Redisはserver.clients_pending_writeをチェックして、応答する必要のあるクライアントがあるかどうかを確認します。 、返信する各クライアントのserver.clients_pending_writeリストをポイントし、そこからクライアントを1つずつ削除し、writeToClient(c、0)を使用して返信するコンテンツに返信すると便利です。

int writeToClient(client *c, int handler_installed) {
    ...    nwritten = connWrite(c->conn,c->buf+c->sentlen,c->bufpos-c->sentlen);    ...static inline int connWrite(connection *conn, const void *data, size_t data_len) {
    return conn->type->write(conn, data, data_len);
}static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
    int ret = write(conn->fd, data, data_len);
    ...}

スレッドI / Oスケッチ

I / Oの問題とスレッド化されたI / Oの導入

Redisにパフォーマンスの問題があると言いたい場合は、I / Oの観点から、他のデータベースのようにディスクを使用しないため、ディスクI / Oの問題はありません。データがバッファに入る前、およびバッファからソケットに書き込むとき、一定量のネットワークI / Oがあります。特に、I / Oの書き込みはパフォーマンスに大きな影響を与えます。以前は、ネットワークI / Oオーバーヘッドを削減するためにパイプラインを使用するか、パフォーマンスを向上させるためにRedisをRedisクラスターとして展開することを検討していました。

Redis 6.0以降、スレッドI / Oの導入により、Redisはネットワークの読み取りと書き込みのスレッド化のサポートを開始し、コマンドのシングルスレッド実行を維持しながら、より多くのスレッドがアクションのこの部分に参加できるようにしました。このような変更により、パフォーマンスがある程度向上する可能性がありますが、コマンド実行をスレッド化することにより、並列実行の静的な問題を解決するためにロックやその他の方法を導入する必要もなくなります。

スレッド化されたI / Oは何をしているのか

古いバージョンの実装では、Redisはさまざまなクライアントのコマンド実行結果をそれぞれのclient.bufに保存し、応答するクライアントをリストに保存し、最後にbufの内容をイベントループの対応するソケットに書き込みます。 。これに対応して、新しいバージョンでは、Redisは複数のスレッドを使用して操作のこの部分を完了します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

読み取り操作の場合、Redisはclients_pending_readプロパティもサーバーオブジェクトに追加します。読み取りイベントが発生すると、スレッド読み取り条件が満たされているかどうかを判断します。満たされている場合、遅延読み取り操作が実行され、クライアントオブジェクトがserver.clients_pending_readに追加されます。リスト。書き込み操作と同様に、次のイベントループまで読み取り操作を完了するために複数のスレッドが使用されます。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

スレッド化されたI / Oの実装と制限

初期化フェーズ

Redisの起動時に、対応するパラメータ設定が満たされている場合、I / Oスレッドの初期化操作が実行されます。

void initThreadedIO(void) {
    server.io_threads_active = 0;
    if (server.io_threads_num == 1) return;
    if (server.io_threads_num > IO_THREADS_MAX_NUM) {
        serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
                             "The maximum number is %d.", IO_THREADS_MAX_NUM);
        exit(1);
    }...

Redisは、構成番号がマルチスレッドI / Oを有効にするための要件を満たしているかどうかを確認するためにいくつかのルーチンチェックを実行します。

...
    for (int i = 0; i < server.io_threads_num; i++) {
        io_threads_list[i] = listCreate();...

スレッド数の長さのio_threads_listリストを作成します。リストの各要素は別のリストLです。Lは、対応するスレッドによって処理される複数のクライアントオブジェクトを格納するために使用されます。

...
        if (i == 0) continue;
...

メインスレッドの場合、初期化操作はここで終了します。

...
        pthread_t tid;
        pthread_mutex_init(&io_threads_mutex[i],NULL);
        io_threads_pending[i] = 0;
        pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
        if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
            serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
            exit(1);
        }        io_threads[i] = tid;    }}...

io_threads_mutexはミューテックスロックのリストです。io_threads_mutex[i]はi番目のスレッドのロックであり、後続のブロッキングI / Oスレッド操作に使用され、初期化後に一時的にロックされます。次に、各スレッドで作成操作を実行します。tidはそのポインターであり、io_threadsリストに保存します。新しいスレッドは常にIOThreadMainメソッドを実行します。最後に説明します。

読み取り/書き込み

マルチスレッドの読み取りと書き込みは、主にhandleClientsWithPendingReadsUsingThreads()とhandleClientsWithPendingWritesUsingThreads()で行われます。この2つはほぼ対称であるため、ここでは読み取り操作についてのみ説明します。興味のある学生は、書き込み操作の違いとその理由を確認できます。 。

int handleClientsWithPendingReadsUsingThreads(void) {
    if (!server.io_threads_active || !server.io_threads_do_reads) return 0;
    int processed = listLength(server.clients_pending_read);
    if (processed == 0) return 0;
    if (tio_debug) printf("%d TOTAL READ pending clients\n", processed);
...

同様に、Redisは、スレッド化された読み取りと書き込みが有効で、スレッド化された読み取りが有効になっているかどうか(前者が有効になっている場合のみ、書き込み操作のみがスレッド化されている)、および読み取りを待機しているクライアントがあるかどうかを定期的にチェックします。

...
    listIter li;    listNode *ln;    listRewind(server.clients_pending_read,&li);    int item_id = 0;
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id],c);        item_id++;    }...

ここで、server.clients_pending_readのリストは、トラバースに便利なリンクリストに変換され、リストの各ノード(* clientオブジェクト)がラウンドロビンと同様の方法で各スレッドに割り当てられます。各クライアントの読み取りと書き込みのシーケンスを保証する必要はありません。 、コマンドの到着順序はserver.clients_pending_read / writeリストに記録されており、今後この順序で実行されます。

...
    io_threads_op = IO_THREADS_OP_READ;
...

ステータスフラグを設定して、マルチスレッド読み取りの現在のステータスを識別します。マークが存在するため、RedisのスレッドI / Oは瞬時に読み取りまたは書き込み状態になり、スレッドの一部で読み取りまたは書き込みを行うことはできません。

...
    for (int j = 1; j < server.io_threads_num; j++) {
        int count = listLength(io_threads_list[j]);
        io_threads_pending[j] = count;    }...

スレッドごとに処理する必要があるクライアントの数を記録します。異なるスレッドが保留中の長さが0でないことを読み取ると、処理を開始します。jは1から始まることに注意してください。これは、メインスレッドが待機中のタスクの数を知らなくても、このメソッドでタスクを同期してすぐに完了するため、0メインスレッドの保留中の長さが常に0であることを意味します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

...
    listRewind(io_threads_list[0],&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        readQueryFromClient(c->conn);
    }    listEmpty(io_threads_list[0]);
...

これで、メインスレッドは、処理するクライアントの処理を終了します。

...
    while(1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += io_threads_pending[j];        if (pending == 0) break;
    }    if (tio_debug) printf("I/O READ All threads finshed\n");
...

待機中のループでスタックし、保留中は各スレッドの残りのタスクの合計に等しくなります。すべてのスレッドにタスクがない場合、このラウンドのI / O処理は終了します。

...
    while(listLength(server.clients_pending_read)) {
        ln = listFirst(server.clients_pending_read);        client *c = listNodeValue(ln);
        c->flags &= ~CLIENT_PENDING_READ;
        listDelNode(server.clients_pending_read,ln);        if (c->flags & CLIENT_PENDING_COMMAND) {
            c->flags &= ~CLIENT_PENDING_COMMAND;
            if (processCommandAndResetClient(c) == C_ERR) {
                continue;
            }        }        processInputBuffer(c);
    }...

connの内容をそれぞれのスレッドで対応するクライアントのclient.querybuf入力バッファーに読み込んだので、server.clients_pending_readリストをトラバースし、コマンドをシリアルに実行し、同時にクライアントをリストから削除できます。

...
    server.stat_io_reads_processed += processed;    return processed;
}

処理が完了すると、処理された数量が統計属性に追加されて返されます。

IOThreadMain

各スレッドの特定の作業内容はこれまで説明されておらず、常にIOThreadMainのループに閉じ込められ、読み取りと書き込みを実行する時間を待ちます。

void *IOThreadMain(void *myid) {
    long id = (unsigned long)myid;
    char thdname[16];
    snprintf(thdname, sizeof(thdname), "io_thd_%ld", id);
    redis_set_thread_title(thdname);    redisSetCpuAffinity(server.server_cpulist);...

通常どおり、初期化コンテンツを実行します。

...
    while(1) {
        for (int j = 0; j < 1000000; j++) {
            if (io_threads_pending[id] != 0) break;
        }        if (io_threads_pending[id] == 0) {
            pthread_mutex_lock(&io_threads_mutex[id]);
            pthread_mutex_unlock(&io_threads_mutex[id]);
            continue;
        }        serverAssert(io_threads_pending[id] != 0);
        if (tio_debug) printf("[%ld] %d to handle\n", id, (int)listLength(io_threads_list[id]));
...

スレッドは保留中のクライアントリストの長さを検出し、待機キューの長さが0より大きい場合に実行します。それ以外の場合は、エンドレスループの先頭に到達します。

ここでは、ミューテックスロックを使用してメインスレッドにロックする機会を与え、I / Oスレッドがpthread_mutex_lock()の実行でスタックし、I / Oスレッドが動作を停止できるようにします。

...
        listIter li;        listNode *ln;        listRewind(io_threads_list[id],&li);        while((ln = listNext(&li))) {
            client *c = listNodeValue(ln);
            if (io_threads_op == IO_THREADS_OP_WRITE) {
                writeToClient(c,0);
            } else if (io_threads_op == IO_THREADS_OP_READ) {
                readQueryFromClient(c->conn);
            } else {
                serverPanic("io_threads_op value is unknown");
            }        }...

io_threads_list [i]のクライアントリストを、トラバースしやすいリンクリストに変換し、1つずつトラバースします。io_threads_opフラグを使用して、マルチスレッド読み取りとマルチスレッド書き込みのどちらを実行するかを決定し、処理するクライアントの操作を完了します。

...
        listEmpty(io_threads_list[id]);
        io_threads_pending[id] = 0;
        if (tio_debug) printf("[%ld] Done\n", id);
    }}

処理するクライアントリストをクリアし、保留中の番号を0に変更して、このラウンドの操作を終了します。

制限

コードを見ると、スレッドI / Oの有効化は、次の条件の影響を受けます。

  • 構成アイテムのio-threadsは1より大きい必要があります。そうでない場合、I / Oの読み取りと書き込みにシングルスレッド操作を引き続き使用します。
  • 構成アイテムio-threads-do-readsは、読み取りI / Oがスレッドを使用するかどうかを制御します
  • postponeClientRead()CLIENT_PENDING_READクライアントCLIENT_PENDING_READ
  • handleClientsWithPendingWritesUsingThreads()stopThreadedIOIfNeeded()server.clients_pending_write
  • initThreadedIO()サーバーio_threads_active server.io_threads_active server.io_threads_active

性能試験

パフォーマンステスト用に不安定なバージョンのRedisをコンパイルしました。テストツールはRedisに付属のredis-benchmarkであり、統計出力のRPS値が参照として使用されます。

Server实例: AWS / m5.2xlarge / 8 vCPU / 32 GB
Benchmark Client实例: AWS / m5.2xlarge / 8 vCPU / 32 GB
Command: redis-benchmark -h 172.xx.xx.62 -p 6379 -c 100 -d 256 -t get,set -n 10000000 --threads 8

スレッドI / OオフとスレッドI / Oオン

元のシングルスレッドI / Oと2スレッド/ 4スレッドのスレッドI / Oのパフォーマンスを比較し、その結果を図に示します。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

io-threads-do-readsオプションをオンにすると、スレッド化されたI / Oは読み取り操作に作用します。これにより、パフォーマンスもさらに向上しますが、書き込みI / Oのスレッド化は大幅に向上しません。さらに、テストに大量のペイロード(-d 8192)を使用しようとしましたが、結果の増加率に大きな違いはないことがわかりました。

スレッドI / OとRedisクラスター

これまで、開発者は、Redisクラスターを単一のインスタンスにデプロイすることでRedisがより多くのCPUリソースを使用できるようにしようとしました。また、このシナリオでのパフォーマンスを比較しようとしました。

牛革! Redis6.0が大幅なパフォーマンス向上を実現する方法

 

新しいバージョンでは、redis-benchmarkも更新され、Redisクラスターのテストがサポートされています。クラスターモードと構成は、-clusterパラメーターをオンにすることで検出できます。この一連の比較テストでは、単一インスタンスのクラスター構築の強力なパフォーマンスを確認しました。実際のテストでは、3つのプロセスのCPU使用率はすべて80%〜90%であり、まだ改善の余地があることを示しています。代わりにテストパラメータ-c512を使用すると、クラスタは400,000RPSを超えて実行できます。テストと実際の使用法は異なり、クラスターの構築時にスレーブを含めないことを選択しましたが、いくつかのモデルでは、クラスターの構築でネットワークI / Oとコマンドの実行にマルチスレッドを実際に使用できることがわかります。 、パフォーマンスの向上も最大です。

まとめと考察

Redis6.0によって導入されたスレッドI / Oは、ソケットの読み取り、書き込み、およびスレッド化を遅らせます。これにより、ネットワークI / Oの方向でRedisに一定のパフォーマンスの向上がもたらされ、使用のしきい値が比較的低くなります。ユーザーはあまり多くの変更を加える必要がありません。つまり、アイドル状態のスレッドリソースは、ビジネスに影響を与えることなく、何の役にも立ちません。

一方、テスト結果から判断すると、この部分の改善では、特に多くのビジネスシナリオでのRedisのパフォーマンスが悪くないことを考えると、Redis5またはRedis3のユーザーがアップグレードする十分な動機を持てない可能性があります。ボトルネックと新しいバージョンの利点は大規模に検証されていません。これは、エンタープライズアプリケーションでより多くのユーザーが注目するサービスの安定性に必然的に影響します。同時に、TIOの改善とクラスターのパフォーマンスの間には一定のギャップがあるようです。これにより、既にクラスターアーキテクチャーにいるエンタープライズユーザーはこの機能を無視する可能性があります。

しかし、いずれにせよ、ユーザーは間違いなく、より多くの新機能、より多くの最適化と改善がRedisに表示されることを喜んでいます。一貫した安定性を維持することを前提に、このバージョンはRedisの誕生以来最大のアップデートと言えます。RESP3、ACL、SSLなどのスレッドI / Oだけでなく、より多くのアプリケーションシナリオでこれらの新機能を楽しみにしています。ダウンロードは宣伝、検証、使用されており、将来のバージョンがユーザーにより多くの驚きとより良い体験をもたらすことを願っています。

さらに読む:Redisを理解する

C / Cのような言語を使用したことがない開発者として、Redisの簡潔なコードと詳細なコメントは、その実装を読んで理解するのに非常に役立ちました。記事の最後に、リードを学ぶためのいくつかの方法、ツール、方法を共有したいと思います。

README.mdは、main()メソッドのグローバル検索ではなく、Redisを理解するためのエントリポイントである必要があります。Redis内部の概要の内容に注意してください。ここでは、Redisのコード構造を紹介します。Redisの各ファイルは「一般的なアイデア」です。この記事では、server.cとnetwork.cのロジックとコードの一部を紹介しました。変換に関連するAof.cとrdb.c、データベースに関連するdb.c、Redisオブジェクトに関連するobject.c、およびレプリケーションに関連するreplication.cはすべて注意を払う価値があります。Redisを含む他のコマンドは、コード化された形式でREADME.mdにあり、コードをさらに読んだときにすばやく見つけることができます。

ドキュメンテーションホームページ[1]とredis-docリポジトリ[2]は、Redisドキュメントのコレクションです。後者のtopicsディレクトリには多くの興味深いトピックがあることに注意してください。私の「興味深い」の定義は次のような記事です。

  • Redisクラスター仕様[3]
  • Redisサーバー支援クライアント側キャッシング[4]

開発者として、徹底的な学習の段階で、これらのコンテンツは、誰もが「使用する」から「理解する」に変化するようにし、Redisがさらに多くのことができることを発見することができます。したがって、ソースコードを読んでデバッグする時間がない場合は、トピックの下にある60を超えるドキュメントを読むことが、Redisを理解するための最速の方法である可能性があります。

最後に、ここで確認できる場合は、Redisソースコードに少し興味があるかもしれません。私はC言語がわからないので、IDEを使用してmain()にブレークポイントを設定してから、プロセスの最初を確認することを選択するかもしれません。実際、私はこれを行いました。コードの他のいくつかの重要なポイントが実際にこの記事に登場しています:

  • main()、開始点
  • initServer()、初期化
  • aeMain()、イベントループ
  • readQueryFromClient()、読み取りイベントのハンドラー
  • processInputBuffer()、コマンド処理のエントリポイント

この記事のようにネットワークのコンテンツを理解したい場合は、aeMain()で中断してから、network.cのメソッドに焦点を当てることができます。特定のコマンドに関連するコンテンツに注意を払いたい場合は、processInputBuffer()で中断してから、 $ command.cまたは同様のファイルのメソッドに注意してください。コマンドメソッドの命名形式は、非常に簡単に見つけることができるREADME.mdファイルにも導入されています。永続性、レプリケーションなど、頻繁に発生するその他のアクションは、コマンドの実行の前後または時間内に発生する可能性があり、beforeSleep()でも発生する可能性があります。server.hで定義されたredisServerとclientは、Redisの2つの非常に重要な構造です。ビジネスでは、多くのコンテンツがそれらの属性に関連する操作に変換されるため、特に注意してください。

おすすめ

転載: blog.csdn.net/sinat_37903468/article/details/108962989