Redisの概要
公式ウェブサイトでのRedisの紹介:
Redisは、データベース、キャッシュ、およびメッセージミドルウェアとして使用できるメモリベースのストレージシステムです。Redisは、文字列、ハッシュ、リスト、セット、ソートされたセット、ビットマップ、ハイパーログログ、地理などの複数のオブジェクトとデータ構造を提供します。
Redisには、組み込みのレプリケーション、luaスクリプト、LRUドライバー、トランザクション、およびさまざまなレベルのディスク永続化機能があり、SentinelおよびRedisクラスタークラスターを通じてRedisの高可用性を保証します。
Redisスレッドモデル
RedisサービスはReactorと同様の方法で実装され、各ネットワーク要求はファイル記述子FDに対応します。I / O多重化モジュールは、複数のFDを同時に監視します。accept、read、write、closeなどのファイルイベントが生成されると、IO多重化プログラムは最初にFD対応イベントをアクティブにし、イベントキューに配置します。プロセッサは、対応するコールバック関数を呼び出して処理し、最後に結果を返します。
- I / O多重化を使用して、複数のソケットを同時に監視し、ソケットによって現在実行されているイベントに従って、ソケットに対応するイベントハンドラーを選択します。
- ときにリスニングソケットを実行する準備ができて
accept
、read
、write
、close
などの操作では、ソケットI / O多重化プログラムは、ソケットファイル転送ディスパッチャイベントにキューで、かつ秩序ある方法で、すべてのイベントを生成します。 - ファイルイベントプロセッサは、対応するプロセッサを呼び出して、I / O多重化からのスコケットイベントタイプに従ってイベントを処理します。
- ファイルイベントプロセッサには、コマンド要求プロセッサ、コマンド応答プロセッサ、接続応答プロセッサなどが含まれ、処理が完了すると結果が返されます。
したがって、Redisのシングルスレッドモデルは、主に、ネットワークIOイベント処理とキーと値のペアの読み取りと書き込みが1つのスレッドで完了し、永続化や非同期削除などの他の操作が追加のスレッドで完了することを意味します。
Redisがシングルスレッドモデルを非常に高速に使用しているのはなぜですか?
1.シングルスレッドモデルを使用する理由
- マルチスレッド!=高速。システム内のリソースにアクセスする複数のスレッドがある場合、共有リソースの正確性を確保するために同様のロックメカニズムが必要であり、これにより追加のオーバーヘッドが発生します。
- 頻繁なコンテキスト切り替えと複数のスレッド間の競合により、追加のリソース消費が発生します。
- Redisはメモリ操作に基づいており、主にネットワークIOに時間がかかるため、CPUは主な問題ではありません。したがって、単一のスレッドを使用するだけで、何百万ものコマンドを処理できます。
2.シングルスレッドモデルが非常に高速なのはなぜですか
- 純粋なメモリ操作。Redisはメモリ操作に基づいており、応答時間はns以内で、QPSは10W +に達する可能性があります。
- 効率的なデータ構造:ハッシュテーブルやジャンプテーブルなどの効率的な操作のためのデータ構造を提供します。
- シングルスレッドは、リソースの競合や複数のスレッド間での頻繁なスレッドコンテキスト切り替えの問題を回避します。
- IO多重化メカニズム:ネットワークIO操作で多数のクライアント要求を同時に処理して高スループットを実現できるようにします。
Redis6マルチスレッドモデル
Redis 6はマルチスレッド操作を提供しますが、Redisは依然としてシングルスレッドモデルであることに注意してください。
Redisのマルチスレッドモードは、主にネットワークリクエストの受信、コマンドの解析、コマンド実行結果の出力に使用されます。マルチスレッド実行として構成できます。Redisは、これらを主な時間のかかるポイントと公式に見なしています。ただし、各コマンドの実行は依然としてシングルスレッドです。
(画像ソース:「GangJingとのRedisマルチスレッドについての話」)
Linuxの多重化
Linux多重化技術とは、ソケット接続が確立された後、ソケットによって生成された複数のファイル記述子FDの後続の監視を指します。ファイル記述子が読み取り/書き込みの準備ができると、対応するデータがユーザースペースにコピーされ、読み取り/書き込み操作が行われます。 。ネットワーク要求を処理するとき、select関数を呼び出すプロセスはブロックされます。
Linuxは、select、poll、epollなどの実装メソッドを提供します。これらは本質的に同期IOメソッドであり、読み取りと書き込みイベントの準備ができた後、すべてが読み取りと書き込みを担当する必要があります。この読み取りと書き込みのプロセスはブロックされます。
呼び出しの選択プロセスは次のとおりです。
- まず、監視が必要なfd_setをユーザースペースからカーネルにコピーし、コールバック関数を登録します。
- すべてのfdをトラバースし、pollメソッド(socket_pollなど)を呼び出します。読み取りおよび書き込み可能なマスクがある場合は、このマスクをfd_setに割り当てます。
- 最後に、fd_setをカーネルスペースからユーザースペースにコピーします
selectとepollの違い:
- プロセスを選択して開くことができる接続の最大数は制限されていますが(32ビットマシンのサイズは3232です)、epoll1Gメモリマシンは約100,000の接続を開くことができます
- selectが呼び出されるたびに、すべての接続が線形にトラバースされるため、selectはFDコレクションをユーザーモードからカーネルモードにコピーする必要があります。FDコレクションが大きい場合、トラバース速度は非常に遅くなります。epollはコールバック関数をに登録します。 FDの場合、このメカニズムメカニズムにより効率が向上します。つまり、アクティブで使用可能なFDのみがコールバック関数を呼び出すため、FDの数を増やしても効率は低下しません。
(selectとepollの違いについては、この記事を読むことができます:select、poll、epollの詳細な理解と違い)
Redis I / O多重化ソースコード分析
Redis IO多重化モデル(epoll)
Redisはイベント駆動型メカニズムを使用して、evport、epoll、kqueue、selectの4つのイベント駆動型モデルを提供します。
epollモデル実装のソースコード(ae_epoll.cファイル内)を見てみましょう。
1.epollインスタンスを作成します。
static int aeApiCreate(aeEventLoop *eventLoop) {
// 省略部分代码
state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
// 调用
eventLoop->apidata = state;
return 0;
}
2.監視イベントを追加し、ファイル記述子と対応するイベントを対応するIOマルチプレックスに追加します
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
aeApiState *state = eventLoop->apidata;
// 省略部分代码
mask |= eventLoop->events[fd].mask; /* Merge old events */
// 添加AE读写事件
if (mask & AE_READABLE) ee.events |= EPOLLIN;
if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
ee.data.fd = fd;
if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
return 0;
}
3.イベントがトリガーされるのを待ち、イベントを受信した後にイベントを保存します
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
// 省略部分代码
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = state->events+j;
// 将epoll事件转换成AE事件
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
// 将事件保存在fired数组中,后续处理会用到该数组
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
}
return numevents;
}
4.イベント処理
main関数は、aeMain()関数を呼び出すことによって監視されます。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
// 循环监听
while (!eventLoop->stop) {
// 调用IO多路复用,如果返回事件,就激活事件处理器进行处理
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}
aeProcessEvents関数はIO多重化APIを呼び出して監視します。IO多重化がイベントを返すと、aeProcessEventsは各アクティベーションイベントのコールバック関数を実行して処理します。
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
// 省略部分代码
// 调用IO多路复用 API,获取激活事件。事件保存在eventLoop->fired[]数组中
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int fired = 0; /* Number of events fired for current fd. */
int invert = fe->mask & AE_BARRIER;
// 先调用回调函数执行可读事件
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
}
// 再调用回调函数执行可写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert) {
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
if ((fe->mask & mask & AE_READABLE) &&
(!fired || fe->wfileProc != fe->rfileProc))
{
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
processed++;
}
}
// 处理超时事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}