Memcached は、データベースの負荷を軽減し、動的システムのパフォーマンスを向上させるために、danga.com (LiveJournal を運営する技術チーム) によって開発された分散メモリ オブジェクト キャッシング システムです。これについては、多くの人が使ったことがあると思いますが、この記事の目的は、memcached の実装とコード分析を通じて、この優れたオープンソース ソフトウェアをより深く理解し、ニーズに応じてさらに最適化することです。最後に、BSM_Memcache 拡張機能の分析を通じて、memcached の使用についての理解を深めます。
この記事の内容の中には、より優れた数学的基礎が必要な場合があります。
◎Memcached とは何ですか?
この問題について詳しく説明する前に、まず Memcached が「何ではない」のかを理解する必要があります。多くの人が SharedMemory のようなストレージ キャリアとして使用していますが、memcached はデータの整理に同じ「Key=>Value」メソッドを使用しますが、共有メモリや APC などのローカル キャッシュとは大きく異なります。 Memcached は分散型です。つまり、ローカルではありません。ネットワーク接続に基づいてサービスを完了します (もちろん、ローカルホストを使用することもできます)。これは、アプリケーションに依存しないプログラムまたはデーモン プロセス (デーモン モード) です。
Memcached は libevent ライブラリを使用してネットワーク接続サービスを実装し、理論的には無制限の数の接続を処理できます。ただし、Apache とは異なり、安定した継続的な接続を重視しているため、実際の同時実行機能は制限されています。保守的な環境では、memcached の最大同時接続数は 200 ですが、これは Linux スレッドの機能に関連しており、この値は調整できます。 libevent については、関連ドキュメントを参照してください。 Memcached のメモリ使用量も APC とは異なります。 APC は共有メモリと MMAP に基づいており、Memcachd は独自のメモリ割り当てアルゴリズムと管理方法を備えており、通常、各 memcached プロセスは 2GB のメモリ空間を管理できます。より多くのスペースが必要な場合は、プロセスの数を増やすことができます。
◎Memcached はどのような場合に適していますか?
memcached は多くの場合悪用されており、当然、それに対する苦情が生じます。よくフォーラムで「効率化の方法」みたいな投稿をしている人を見かけますが、「memcached を使えばいい」という返事が返ってくるのですが、使い方、どこで、何に使うかについては文章がありません。 Memcached は万能薬ではなく、すべての状況に適しているわけでもありません。
Memcached は「分散」メモリ オブジェクト キャッシング システムです。つまり、「分散」する必要がない、共有する必要がない、または単純にサーバーが 1 つしかないアプリケーションの場合、memcached は使用できません。それどころか、ネットワーク接続にもリソース (UNIX ローカル接続であっても) が必要となるため、システムの効率も低下します。 以前のテスト データでは、memcached のローカル読み取りおよび書き込み速度は、直接 PHP メモリ アレイよりも数十倍遅いのに対し、APC および共有メモリ メソッドは直接アレイと同様であることが示されました。ローカルレベルのキャッシュのみの場合、memcached の使用は非常に不経済であることがわかります。
Memcached は、データベースのフロントエンド キャッシュとしてよく使用されます。データベースよりも SQL 解析、ディスク操作、その他のオーバーヘッドがはるかに少なく、メモリを使用してデータを管理するため、大規模なシステムではデータベースを直接読み取るよりも優れたパフォーマンスを実現できます。多くの場合、memcached によりデータベースの負荷が大幅に軽減され、システムの実行効率が向上します。さらに、memcached はサーバー間でデータを共有するための記憶媒体としてよく使用されます。たとえば、SSO システムでシステムのシングル サインオン状態を保存するデータを memcached に保存し、複数のアプリケーションで共有できます。
memcached はデータの管理にメモリを使用するため、サーバーが再起動されるか memcached プロセスが終了するとデータが失われるため、memcached を使用してデータを永続化することはできないことに注意してください。多くの人は、memcached のパフォーマンスがメモリとハードディスクの比較と同様に非常に優れていると誤解しています。実際、memcached の実際のボトルネックはネットワークにあります。ディスクデータベースシステムと比較して、過剰なオーバーヘッドがなく、直接読み書きする方法がないため、非常に大量のデータを簡単に処理できるという利点があります。多くの場合、2 ギガビットのネットワーク帯域幅があり、それらはすべて完全にロードされており、memcached プロセス自体は多くの CPU リソースを占有しません。
◎Memcached の仕組み
以下のセクションでは、読者が memcached のソース コードのコピーを用意することをお勧めします。
Memcached は従来のネットワーク サービス プログラムで、起動時に -d パラメーターを使用すると、デーモン プロセスとして実行されます。デーモン プロセスの作成は daemon.c によって完了します。このプログラムにはデーモン関数が 1 つだけあり、非常に単純です (特別な指示がない場合、コードは 1.2.1 に準拠します)。
コード:[クリップボードにコピー]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
デーモン(nochdir、noclose)
int nochdir、noclose;
{
int fd;
スイッチ (fork()) {
ケース-1:
(-1) を返します。
ケース0:
壊す;
デフォルト:
_終了(0);
if
(setsid() == -1)
(!nochdir) の場合は
(-1) を返します。
(void)chdir("/");
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(void)close(fd);
}
リターン (0);
}
この関数がプロセス全体をフォークした後、親プロセスが終了し、STDIN、STDOUT、および STDERR を空のデバイスに再配置し、デーモンが正常に確立されます。
Memcached 自体の起動プロセスは、memcached.c の main 関数で次のようになります。
1. settings_init() を呼び出して、初期化パラメータを設定します。
2. 起動コマンドからパラメータを読み込み、設定値を設定します
3. LIMITパラメータを設定します
4. ネットワークソケット監視を開始します (非ソケットパスが存在する場合) (UDP モードは 1.2 以降でサポートされます)
5. ユーザー ID を確認します (Memcached ではルート ID の開始が許可されていません)。
6.socketpath が存在する場合は、UNIX ローカル接続 (Sock パイプ) を開きます。
7. -d モードで起動した場合は、デーモン プロセスを作成します (上記のようにデーモン関数を呼び出します)。
8. アイテム、イベント、ステータス情報、ハッシュ、コネクション、スラブの初期化
9. 設定で管理が有効になっている場合は、バケット配列を作成します
10. メモリページをロックする必要があるかどうかを確認します
11. シグナル、接続の初期化、キューの削除
12. デーモンモードの場合、プロセスのプロセスID
13. イベントが開始され、起動プロセスが終了し、main 関数がループに入ります。
デーモン モードでは、stderr がブラック ホールに送信されるため、実行中に目に見えるエラー メッセージはフィードバックされません。
memcached.c のメイン ループ関数は drive_machine です。受信パラメータは現在の接続を指す構造体ポインタであり、アクションは状態メンバーのステータスに基づいて決定されます。
Memcached は、一連のカスタム プロトコルを使用してデータ交換を完了します。そのプロトコル ドキュメントは、http: //code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txtで参照できます。API
では、改行記号はrn
◎Memcached のメモリ管理方式
Memcached は、効率を向上させるために、データを書き込むたびに malloc を使用するのではなく、事前適用およびグループ化方式を使用してメモリ空間を管理します。 . 、データを削除するときにポインターを解放します。 Memcached は、スラブ -> チャンク編成方法を使用してメモリを管理します。
1.1 と 1.2 の slabs.c のスラブ空間分割アルゴリズムにはいくつかの違いがあります。これについては後ほど個別に紹介します。
スラブは、memcached が一度にメモリに適用する最小単位であるため、memcached はメモリの MB 全体を使用します。各スラブはいくつかのチャンクに分割されており、各チャンクには項目が格納されます。各項目には項目構造、キー、値も含まれます (memcached の値は単なる文字列であることに注意してください)。スラブは独自の ID に従ってリンク リストを形成し、これらのリンク リストは ID に従ってスラブクラス配列に掛けられます。構造全体は 2 次元配列に似ています。スラブクラスの長さは、1.1 では 21、1.2 では 200 です。
スラブの初期チャンク サイズは、1.1 では 1 バイト、1.2 では 80 バイトです。1.2 には係数値があり、
1.1 では、チャンク サイズは初期サイズ * 2^n で表されます。 classid、つまり、ID 0 のスラブのチャンク サイズは 1 バイト、ID 1 のスラブのチャンク サイズは 2 バイト、ID 2 のスラブのチャンク サイズは 4 バイトです...ID のスラブ20 のチャンク サイズは 4 バイトです。サイズは 1MB です。つまり、スラブには ID 20 のチャンクが 1 つだけ存在します。
CODE:[クリップボードにコピー]void slabs_init(size_t limit) {
int i;
int サイズ = 1
;
for(i=0; i<=POWER_LARGEST; i++, size*=2) {
slabclass[i].size = サイズ;
slabclass[i].perslab = POWER_BLOCK / サイズ;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
/* テスト スイートの場合: すでに malloc した量を偽装します */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* 環境変数が指定されていない限り、デフォルトでスラブを事前に割り当てます
テスト用にゼロ以外の値が設定されます */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(制限 / POWER_BLOCK);
}
}
}
1.2 では、チャンク サイズは初期サイズ * f^n として表されます。f は memcached.c で定義される係数、n は classid です。同時に、係数が異なるため、201 個のヘッドすべてを初期化する必要はありません。は変数であり、初期化のみがループします。計算されたサイズはスラブ サイズの半分に達し、id1 から始まります。つまり、id 1 のスラブ、各チャンク サイズは 80 バイト、id 2 のスラブ、各チャンク サイズは 80* f、id は 3 スラブ、各チャンク サイズは 80*f^2、初期化サイズには n バイトのアライメントを保証するための補正値 CHUNK_ALIGN_BYTES があります (結果が CHUNK_ALIGN_BYTES の整数倍であることを保証します)。このように、標準的な状況では、memcached1.2 は id40 に初期化されます。このスラブの各チャンクのサイズは 504692 で、各スラブには 2 つのチャンクがあります。最後に、slab_init 関数は最後に id41 を追加します。これはブロック全体です。つまり、このスラブには 1MB チャンクが 1 つだけあります。
コード:[クリップボードにコピー]void slabs_init(size_t 制限、倍精度係数) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* 係数 2.0 は、デフォルトの memcached 動作を使用することを意味します */
if (係数 == 2.0 && サイズ < 128)
サイズ = 128
;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* 項目が常に n バイトで整列されていることを確認します */
if (サイズ % CHUNK_ALIGN_BYTES)
サイズ += CHUNK_ALIGN_BYTES - (サイズ % CHUNK_ALIGN_BYTES)
;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
サイズ *= 係数;
if (settings.verbose > 1) {
fprintf(stderr, "スラブ クラス %3d: チャンク サイズ %6d パースラブ %5dn",
i、slabclass[i].size、slabclass[i].perslab);
}
power_largest
= i;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* テストスイート用: すでに malloc した量を偽装します */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
#ifndef
DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(制限 / POWER_BLOCK);
}
}
#endif
}
上記からわかるように、memcached のメモリ割り当ては冗長です。スラブが所有するチャンク サイズで割り切れない場合、スラブの最後にある残りのスペースは破棄されます。たとえば、id40 では、2 つのチャンクが占有されます。 1009384 バイト、このスラブには合計 1MB があるため、39192 バイトが無駄になります。
Memcached は、項目の長さを通じてスラブの classid をすばやく見つけるためにこのメソッドを使用してメモリを割り当てます。たとえば、1.2 では項目の長さが 300 バイトであることがわかります。上記の計算方法によれば、id6のチャンクサイズは252バイト、id7のチャンクサイズは316バイト、id8のチャンクサイズは396バイトであるため、id7のスラブに格納する必要があることがわかります。これは、252 ~ 316 バイトのすべての項目を id7 に格納する必要があることを意味します。同様に、1.1 では、256 ~ 512 の間であると計算でき、chunk_size が 512 (32 ビット システム) の id9 に配置される必要があります。
Memcached が初期化されると、スラブも初期化されます (前述したように、メイン関数で slabs_init() が呼び出されます)。 slabs_init() の定数 DONT_PREALLOC_SLABS がチェックされます。これが定義されていない場合は、事前に割り当てられたメモリを使用してスラブが初期化され、定義されているすべてのスラブクラスの ID ごとにスラブが作成されることを意味します。これは、デフォルト環境でプロセスを開始した後、1.2 が 41MB のスラブ領域を割り当てることを意味します。このプロセス中に、ID がまったく使用されていない可能性もあるため、memcached の 2 番目のメモリ冗長性が発生します。スラブはデフォルトで適用され、各スラブは 1MB のメモリを使用します。
スラブが使い果たされ、この ID を使用して新しいアイテムを挿入する必要がある場合、新しいスラブを適用するときに再適用されます。 slab、対応する ID スラブのリンク リストは関数 give_slab_list で指数関数的に増加します。このチェーンの長さは 1 から 2、2 から 4、4 から 8... と変化します。
CODE:[クリップボードにコピー]static int give_slab_list (unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
サイズ_t 新しいサイズ = p->リストサイズ ? p->リストサイズ * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
if (new_list == 0) は 0 を返します。
p->リストサイズ = 新しいサイズ;
p->slab_list = new_list;
}
1を返します。
}
項目を検索するときに、slabs_clsid 関数が使用されます。受信パラメータは項目のサイズであり、戻り値は classid です。このプロセスから、memcached の 3 番目のメモリ冗長性が項目の保存中に発生することがわかります。項目は常にチャンク サイズ以下です。項目がチャンク サイズより小さい場合、スペースが再び無駄になります。
◎MemcachedのNewHashアルゴリズム
Memcachedのアイテムストレージは大きなハッシュテーブルに基づいていますが、その実際のアドレスはスラブ内のチャンクオフセットですが、その位置はprimary_hashtableにあるキーのハッシュ結果に依存します。すべてのハッシュ操作とアイテム操作は assoc.c と items.c で定義されます。
Memcached は、非常に効果的かつ効率的な NewHash と呼ばれるアルゴリズムを使用します。 NewHash 1.1 と 1.2 にはいくつかの違いがありますが、主な実装方法は同じですが、1.2 のハッシュ関数は整理および最適化されており、適応性が向上しています。
NewHash プロトタイプのリファレンス: http://burtleburtle.net/bob/hash/evahash.html 。数学者はいつも少し変わっています (笑)
変換を容易にするために、u4 と u1 という 2 つのデータ型が定義されています。u4 は符号なし長整数、u1 は符号なし文字 (0 ~ 255) です。
特定のコードについては、1.1 および 1.2 ソース コード パッケージを参照してください。
ここで、ハッシュテーブルの長さにも注意してください。1.1 と 1.2 では、HASHPOWER 定数が 20 として定義されており、ハッシュテーブルの長さは 4MB です (ハッシュサイズはマクロです)。 1 は n ビット右にシフトされます)。1.2 では変数 16、つまりハッシュテーブル テーブルの長さは 65536 です。
CODE:[クリップボードにコピー]typedef unsigned long int ub4 /* 符号なし 4 バイト量 */
typedef unsigned char ub1; /* 符号なし 1 バイト量 */
#define hashsize(n) ((ub4)1<<(n))
#define ハッシュマスク(n) (ハッシュサイズ(n)-1)
assoc_init() では、primary_hashtable が初期化されます。対応するハッシュ操作には、項目の読み取りおよび書き込み操作に対応する assoc_find()、assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete() が含まれます。このうち、assoc_find() は、キーとキーの長さに基づいて、対応する項目のアドレスを見つける関数です (C では、関数内で strlen を行う代わりに、文字列と文字列の長さを同時に直接渡すことが多いことに注意してください) )、返されるのは項目構造体ポインターであり、そのデータ アドレスはスラブ内のチャンク上にあります。
items.c はデータ項目の操作プログラムです。各完全な項目にはいくつかの部分が含まれており、item_make_header() で
次
のように定義されます。
nkey: キーの長さ
flags: ユーザー定義のフラグ (実際、このフラグは memcached では有効になっていません)
nbytes: 値の長さ (改行記号 rn を含む)
サフィックス: サフィックスバッファ
nsuffix: 完全な項目のサフィックスの長
さは、キーの長さ + 値の長さ + サフィックスの長さ + 項目構造のサイズ (32 バイト) です。項目の操作は、この長さに基づいてスラブのクラス ID を計算します。
ハッシュテーブル内の各バケットは、item_init() 中に二重リンク リストでハングされ、heads、tails、size の 3 つの配列が 0 に初期化されます。これら 3 つの配列のサイズは定数 LARGEST_ID (デフォルトは 255) です。この値は係数で変更する必要があります)、 item_assoc() が呼び出されるたびに、まずスラブから空きチャンクを取得しようとします。使用可能なチャンクがない場合は、リンクされたリストを 50 回スキャンして、使用可能なチャンクを取得します。 LRU アイテムによって開始され、リンクを解除してから、リンクされたリストに挿入するアイテムを挿入します。
item の refcount メンバーに注意してください。アイテムは、リンクが解除された後、リンクされたリストから削除されるだけで、すぐには解放されません (item_unlink_q() 関数)。
項目は、削除、更新、置換などの一部の読み取りおよび書き込み操作に対応します。もちろん、最も重要なのは割り当て操作です。
item のもう 1 つの特徴は、有効期限があることです。これは、memcached の非常に便利な機能です。セッション ストレージ、操作ロックなど、多くのアプリケーションが memcached のアイテムの有効期限に依存しています。 item_flush_expired() 関数は、テーブル内の項目をスキャンし、期限切れの項目のリンク解除操作を実行します。実際には、以下を取得するときにも時間の判断が必要です。
CODE:[クリップボードにコピー]/* 最も古い_live設定より新しいアイテムは期限切れになります */
void item_flush_expired() {
int i;
item *iter、*next;
if (! settings.oldest_live)
戻る;
for (i = 0; i < LARGEST_ID; i++) {
/* LRU は時間の降順でソートされ、アイテムのタイムスタンプが表示されます。
* は最終アクセス時間より新しいことはないので、歩くだけで済みます
* 最古のライブ時間よりも古い項目に到達するまで戻ります。
* Oldest_live チェックにより、残りのアイテムは自動的に期限切れになります。
*/
for (iter = heads[i]; iter != NULL; iter = next) {
if (iter->time >= settings.oldest_live) {
次 = iter->next;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} それ以外 {
/* 最初の古い項目にヒットしました。次のキューに進みます。
壊す;
}
}
}
}
CODE:[クリップボードにコピー]/* 遅延有効期限切れ/削除ロジックを実行する assoc_find のラッパー */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
item *it = assoc_find(key, nkey);
if (delete_locked) *delete_locked = 0;
if (it && (it->it_flags & ITEM_DELETED)) {
/* 削除ロックのフラグが設定されているので、その状態かどうかを確認してみましょう。
期限を過ぎていますが、5 秒間の delete_timer がまだ終わっていません
まだできました... */
if (!item_delete_lock_over(it)) {
if (delete_locked) *delete_locked = 1;
それ = 0;
}
}
if (それ && settings.oldest_live && settings.oldest_live <= current_time &&
it->time <= settings.oldest_live) {
item_unlink(it);
それ = 0;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink(it);
それ = 0;
}
それを返してください。
}
Memcached のメモリ管理方法は非常に洗練されており、効率的です。これにより、システム メモリの直接割り当ての数が大幅に削減され、関数のオーバーヘッドとメモリの断片化の可能性が軽減されます。ただし、この方法では余分な無駄が発生しますが、大規模なシステムではこの無駄はわずかです。アプリケーション。
◎Memcached の理論的なパラメータ計算方法には、
memcached の動作に影響を与えるいくつかのパラメータがあります:
定数 REALTIME_MAXDELTA 60*60*24*30
conn_init() の
最大有効期限は 30 日
freetotal (=200)
最大同時接続数
定数 KEY_MAX_LENGTH 250
最大キー長
設定係数 (=1.25)
settings.maxconns (=1024)
のステップ サイズに影響します。
最大ソフト接続
設定.chunk_size (=48)
id1 のチャンク長 (1.2) を生成するために使用される、保守的に推定されたキー + 値の長さ。 id1 のチャンク長は、この値に項目構造の長さ (32) を加えたもの (デフォルトの 80 バイト) に等しくなります。
定数 POWER_SMALLEST 1
最小クラス ID (1.2)
定数 POWER_LARGEST 200
最大クラス ID (1.2)
定数 POWER_BLOCK 1048576
デフォルトのスラブ サイズ
定数 CHUNK_ALIGN_BYTES (sizeof(void *))
範囲外を防ぐために、チャンク サイズがこの値の整数倍であることを確認してください (void * の長さはシステムによって異なります。標準の 32 ビット システムでは 4 です)
定数 ITEM_UPDATE_INTERVAL 60
キュー更新間隔
定数 LARGEST_ID 255
リンクされたリスト内の項目の最大数 (この値は最大のクラス ID より小さくすることはできません)
変数 hashpower (1.1 の定数 HASHPOWER)
ハッシュテーブルのサイズを決定するには、
上記で紹介した内容とパラメーター設定に基づいて、次のような結果を計算できます。
1. memcached に保存できるアイテムの数にソフトウェアの上限はありません。前のステートメントは 100 万です。間違っている。
2. NewHash アルゴリズムに一様な衝突があると仮定すると、項目を見つけるためのサイクル数は、項目の総数をハッシュテーブル サイズ (ハッシュパワーによって決定される) で割った値となり、これは線形になります。
3. Memcached は許容できる最大項目を 1MB に制限しており、1MB を超えるデータは無視されます。
4. Memcached のスペース使用率はデータ特性と大きな関係があり、DONT_PREALLOC_SLABS 定数にも関連しています。 最悪の場合、198 個のスラブが無駄になります (すべてのアイテムが 1 つのスラブに集中し、199 個の ID がすべて完全に割り当てられます)。
◎memcached の固定長の最適化
ここまでの説明をもとに、memcached についての理解がさらに深まりました。深い理解に基づいてのみ最適化できます。
Memcached 自体は、データの特性によれば、「パブリック指向」の設計であると言えますが、多くの場合、データはそれほど「ユニバーサル」ではありません。不均一な分散、つまりデータ長が複数の領域に集中している (ユーザー セッションの保存など)。もう 1 つのより極端な状態は、等しい長さのデータ (固定長のキー値、固定長データなど) です。アクセス、オンライン統計、または実行ロックで見られます)。
ここでは主に固定長データの最適化ソリューション (1.2) を検討します。集中型分散可変長データは参考用であり、実装は簡単です。
固定長データを解決するには、最初にスラブ割り当ての問題を解決する必要があります。最初に確認する必要があるのは、使用を最大化するためには、異なるチャンク長を持つそれほど多くのスラブが必要ないということです。リソースの場合、チャンクとアイテムの長さが等しいことが最適であるため、最初にアイテムの長さを計算します。
項目の長さを計算するアルゴリズムは以前にもありましたが、文字列の長さに加えて、32 バイトの項目構造の長さを追加する必要があることに注意してください。
200 バイトの等しい長さのデータを保存する必要があると計算したとします。
次のステップは、スラブの classid とチャンク長の関係を変更することです。オリジナルのバージョンでは、チャンク長と classid の間に対応関係がありますが、すべてのチャンクが 200 バイトに設定されている場合、この関係は存在しません。 1 つの方法は、ストレージ構造全体に固定 ID のみを使用することです。つまり、199 スロットのうちの 1 つだけを使用して、追加の事前割り当ての無駄を避けるために DONT_PREALLOC_SLABS を定義する必要があります。もう 1 つの方法は、アイテムからクラス ID を決定するハッシュ関係を確立することです。キーとして長さを使用することはできません。キーの NewHash 結果などの変数データを使用することも、キーに基づいて直接ハッシュを実行することもできます。固定長データのキーも同じ長さである必要があります)。ここでは簡単にするために、最初の方法を選択します。この方法の欠点は、データの量が非常に多い場合、スラブ チェーンが非常に長くなるということです (すべてのデータが密集しているため)。 1 つのチェーン)、移動のコストは比較的高くなります。
3 つのタイプのスペース冗長性については、前に紹介しました。チャンクの長さをアイテムの長さと同じに設定すると、最初のスペースの無駄の問題が解決されます。では、最初の問題 (スラブに残る) はどうなるでしょうか。これを解決するには、スラブを n 個のチャンクに分割できるように、各スラブのサイズがチャンク長の整数倍に正確に等しくなるように POWER_BLOCK 定数を変更する必要があります。この値が大きすぎると、冗長性が生じます。チャンク長が 200 であるため、POWER_BLOCK の値として 1000000 を選択してください。このようにすると、スラブは 1048576 バイトではなく、100 万バイトになります。 3 つの冗長性の問題がすべて解決され、スペース使用率が大幅に向上します。
固定値 (1 など) を直接返すように slabs_clsid 関数を変更します。
コード:[クリップボードにコピー]unsigned int slabs_clsid(size_t size) {
1を返します。
}
slabs_init 関数を変更し、すべての classid 属性を作成するためにループする部分を削除し、slabclass[1] を直接追加します。
CODE:[クリップボードにコピー] slabclass[1].size = 200 バイト/チャンク;
slabclass[1].perslab = 5000 //1000000/200;
◎Memcached クライアント
Memcached を利用すると、そのプロトコルに従って memcached サーバーに接続し、サービスプロセスにコマンドを送り、上記データを操作することができます。使いやすさを考慮して、memcached にはさまざまな言語に対応した多くのクライアント プログラムが用意されており、さまざまな言語のクライアントが存在します。 C 言語ベースのものには libmemcache と APR_Memcache が含まれ、Perl ベースのものには Cache::Memcached が含まれ、Python、Ruby、Java、C# などの言語もサポートされています。 PHP には、2 つの拡張機能 mcache と PECL memcache だけでなく、PHP によって作成された多数のカプセル化クラスも含まれる最も多くのクライアントがあります。 ここでは、PHP での memcached の使用方法を紹介します。
mcache 拡張機能は、libmemcache に基づいて再カプセル化されます。 。 libmemcache は安定バージョンをリリースしていません。現在のバージョンは 1.4.0-rc2 です。これはここにあります。 libmemcache の非常に悪い特徴は、多くのエラー メッセージを stderr に書き込むことです。通常、lib として使用すると、stderr は通常、Apache のエラー ログなどの他の場所に送られ、libmemcache が自殺するため、異常が発生する可能性があります。 , しかし、そのパフォーマンスは依然として非常に優れています。
mcache 拡張機能は最後に 1.2.0-beta10 に更新されました。作者はおそらく更新を停止しただけでなく、この無責任な拡張機能を入手するために他の場所に行かなければなりませんでした。 。解凍後のインストール方法は通常通り、phpize & configure & make & make install です。必ず最初に libmemcache をインストールしてください。この拡張機能の使用は簡単です。
CODE:[クリップボードにコピー]<?php
$mc = memcache(); // ここでは new が使用されていないことに注意してください。
$mc->add_server('localhost', 11211); // サービスプロセスを追加します。
$mc->add_server('localhost', 11212); // 2 番目のサービス プロセスを追加します。
$mc->set('key1', 'Hello') // key1 を書き込みます => こんにちは
$mc->set('key2', 'World', 10); // key2 => ワールドを書き込み、10 秒で期限切れになります。
$mc->set('arr1', array('Hello', 'World')); // 配列を書き込みます。
$key1 = $mc->get('key1'); // 'key1' の値を取得し、$key1 に代入します。
$key2 = $mc->get('key2'); // 'key2' の値を取得し、それを $key2 に代入します。10 秒を超えると使用できなくなります。
$arr1 = $mc->get('arr1') // 'arr1' 配列を取得します。
$mc->delete('arr1'); // 'arr1' を削除します。
$mc->flush_all(); // すべてのデータを削除します
$stats = $mc->stats(); // サーバー情報を取得します。
var_dump($stats); // サーバー情報は配列です
?>
この拡張機能の利点は、データを保存するときに、ハッシュ結果に基づいて特定のサーバーに配置されるため、分散ストレージと負荷分散を簡単に実装できることです。 。 libmemcache は、CRC32、ELF、Perl ハッシュなどの集中ハッシュ方式をサポートします。
PECL memcache は PECL によってリリースされた拡張機能で、最新バージョンは 2.1.0 で、pecl Web サイトから入手できます。 memcache 拡張機能の使用法は、いくつかの新しい PHP マニュアルに記載されています。これは mcache に非常によく似ており、非常によく似ています。
CODE:[クリップボードにコピー]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) または die (「接続できませんでした」);
$version = $memcache->getVersion();
echo "サーバーのバージョン: ".$version."n";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'テスト';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) または die (「サーバーでのデータの保存に失敗しました」);
echo "データをキャッシュに保存します (データは 10 秒で期限切れになります)n";
$get_result = $memcache->get('key');
echo "キャッシュからのデータ:n
"
;
この拡張機能は、PHP のストリームを使用して memcached サーバーに直接接続し、ソケット経由でコマンドを送信します。 libmemcache ほど完全ではなく、add_server などの分散操作もサポートしていませんが、他の外部プログラムに依存しないため、互換性が高く、比較的安定しています。効率に関しては、大きな違いはありません。
さらに、MemcacheClient.inc.php など、多くの PHP クラスが利用可能であり、その多くは phpclasses.org で見つけることができます。これらは通常、Perl クライアント API を再カプセル化したもので、同様の方法で使用されます。
◎BSM_Memcache
C クライアントの観点から見ると、APR_Memcache は、スレッド ロックとアトミック レベルの操作をサポートし、動作の安定性を確保する非常に成熟した安定したクライアント プログラムです。ただし、APRに基づいています(最後のセクションでは導入されます)。現在、libmemcacheほど幅広いアプリケーションはありませんAPR環境の外で走ることができないからです。ただし、APRとAPR-UtilはAPRのWebサイトからは別にインストールできます。
BSM_MEMCACHEは、Bs.Magicプロジェクトで開発したAPR_MEMCACHEに基づくPHP拡張機能ですが、少なくともAPRがPHP拡張機能に組み込まれています。このプログラムは非常にシンプルで、あまり多くの機能を実行しません。
マルチサーバー分散ストレージをサポートするMCACHE拡張機能とは異なり、BSM_MEMCACHEは各グループのサーバーの複数のグループをサポートしますが、ハッシュメソッドに従ってデータを保存しますが、2つのグループで保存されたデータは同じです。 、ホットバックアップは、すべてのサーバーグループが破損していない限り、1つのサーバーの単一の障害のためにデータを利用できません。もちろん、この関数を実装するコストは、データを追加または削除するたびに、すべてのグループをスキャンする必要があります。通常、次回は入手できます。
bsm_memcacheはこれらの関数のみをサポートします。
コード:[クリップボードにコピー] zend_function_entry bsm_memcache_functions [] =
{
php_fe(mc_get、null)
php_fe(mc_set、null)
php_fe(mc_del、null)
php_fe(mc_add_group、null)
php_fe(mc_add_server、null)
php_fe(mc_shutdown、null)
{null、null、null}
};
MC_ADD_GROUP関数は、整数(実際にはオブジェクトである必要があります、私は怠け者でした〜_〜)は、MC_ADD_SERVERを使用する場合、2つのパラメーターを提供する必要があります。 AddRort)。
コード:[クリップボードにコピー]/**
*サーバーグループを追加します
*/
php_function(mc_add_group)
{
apr_int32_t group_id;
apr_status_t
rv;
{
間違った_param_count;
return_null();
}
group_id = free_group_id();
if(-1 == group_id)
{
return_false;
}
apr_memcache_t *mc;
rv = apr_memcache_create(p、max_g_server、0、&mc
)
;
}
コード:[クリップボードにコピー]/**
*サーバーをグループに追加します
*/
php_function(mc_add_server)
{
apr_status_t rv;
apr_int32_t group_id;
ダブルG;
char *srv_str;
int srv_str_l
;
{
間違った_param_count;
}
if(zend_parse_parameters(zend_num_args()tsrmls_cc、 "ds"、&g、&srv_str、&srv_str_l)==故障)
{
return_false;
}
group_id =(apr_int32_t)g
;
{
return_false;
}
char *host、 *scope;
port
;
if(apr_success == rv)
{
//このサーバーオブジェクトを作成します
apr_memcache_server_t *st;
rv = apr_memcache_server_create(p、host、port、0、64、1024、600、&st);
if(apr_success == rv)
{
if(null == mc_groups [group_id])
{
return_false;
}
//サーバーを追加します
rv = apr_memcache_add_server(mc_groups [group_id]、
if(apr_success == rv)
{
return_true;
}
}
}
return_false;
}
データを設定して削除するときは、すべてのグループをループします。
コード:[クリップボードにコピー]/**
*アイテムをすべてのグループに保存します
*/
php_function(mc_set)
{
char *key、 *value;
int key_l、value_l;
double ttl = 0;
double set_ct = 0
;
{
間違った_param_count;
}
if(zend_parse_parameters(zend_num_args()tsrmls_cc、 "ss | d"、&key、&key_l、&&buale _l、ttl)== fails)
{
return_false;
}
//すべてのオブジェクトにデータを書き込みます
apr_int32_t i = 0;
if(ttl <0)
{
ttl = 0;
}
apr_status_t rv;
for(i = 0; i <max_group; i ++)
{
if(0 == is_validate_group(i))
{
//それを書いてください!
rv = apr_memcache_add(mc_groups [i]、key、value、value_l、(apr_uint32_t)ttl、0);
if(apr_success == rv)
{
set_ct ++;
}
}
}
return_double(set_ct);
}
MC_GETでは、最初にグループをランダムに選択してから、このグループからポーリングを開始します。
コード:[クリップボードにコピー]/**
*ランダムグループからアイテムを取得します
*/
php_function(mc_get)
{
char *key、 *value = null;
int key_l;
apr_size_t
value_l;
{
間違った_param_count;
}
if(zend_parse_parameters(zend_num_args()tsrmls_cc、 "s"、&key、&key_l)==失敗)
{
return_mull();
}
// 試してみます...
//ランダム読み取り
apr_int32_t curr_group_id = random_group();
apr_int32_t i = 0;
apr_int32_t try = 0;
APR_UINT32_Tフラグ;
apr_memcache_t *oper;
apr_status_t rv;
for(i = 0; i <max_group; i ++)
{
try = i + curr_group_id;
try = try%max_group;
if(0 == is_validate_group(try))
{
//値を取得します
oper = mc_groups [try];
rv = apr_memcache_getp(mc_groups [try]、p、(const char *)key、&value_l、0);
if(apr_success == rv)
{
return_string(value、1);
}
}
}
return_false;
}
コード:[クリップボードにコピー]/**
*ランダムグループID
* mc_get()の場合
*/
apr_int32_t random_group()
{
struct timeval TV;
struct timezone tz;
int usec
(
&
tz
)
;
}
bsm_memcacheの使用は、他のクライアントに似ています。
コード:[クリップボードにコピー] <?PHP
$ g1 = mc_add_group();
$ g2 = mc_add_group();
MC_ADD_SERVER($ G1、 'LocalHost:11211');
MC_ADD_SERVER($ g1、 'localhost:11212');
MC_ADD_SERVER($ G2、'10 .0.0.16:11211 ');
MC_ADD_SERVER($ G2、'10 .0.0.17:11211 ')
;
$ key = mc_get( 'key'); //読み取り
MC_DEL( 'key'); //削除します
mc_shutdown(); //すべてのグループをシャットダウンします
?>
apr_memcacheに関する関連情報はここにあり、bsm_memcacheはこのサイトからダウンロードできます。
APR環境はじめに
APRのフルネーム:Apacheポータブルランタイム。これは、Apache Software Foundationによって作成および維持されているクロスプラットフォームC言語ライブラリのセットです。 Apache httpd1.xから抽出され、httpdに依存しません。 APRは、メモリプール、文字列操作、ネットワーク、アレイ、ハッシュテーブルなどの実用的な機能など、使用するための多くの便利なAPIインターフェイスを提供します。もちろん、APACHE2モジュールの開発には、APRがインストールされ、独自のアプリケーションを作成できます。
postscript
これは、月のカレンダーのBingxu年の私の最後の記事です(私の生年月日)。研究の機会と同僚の支援を提供してくれたSina.comに感謝します。
NP02-13-2007博士