Memcached adalah sistem caching objek memori terdistribusi yang dikembangkan oleh danga.com (tim teknis yang mengoperasikan LiveJournal) untuk mengurangi beban database dan meningkatkan kinerja dalam sistem dinamis. Mengenai hal ini, saya yakin banyak orang telah menggunakannya. Tujuan artikel ini adalah untuk mendapatkan pemahaman lebih dalam tentang perangkat lunak open source yang luar biasa ini melalui implementasi dan analisis kode memcached, dan untuk lebih mengoptimalkannya sesuai dengan kebutuhan kita. Terakhir, melalui analisis ekstensi BSM_Memcache, kita akan memperdalam pemahaman kita tentang penggunaan memcached.
Beberapa konten dalam artikel ini mungkin memerlukan landasan matematika yang lebih baik sebagai bantuan.
◎Apa itu Memcached?
Sebelum menguraikan masalah ini, pertama-tama kita harus memahami apa yang "bukan" itu. Banyak orang menggunakannya sebagai pembawa penyimpanan seperti SharedMemory. Meskipun memcached menggunakan metode "Key=>Value" yang sama untuk mengatur data, ini sangat berbeda dari cache lokal seperti memori bersama dan APC. Memcached didistribusikan, artinya bukan lokal. Ini melengkapi layanan berdasarkan koneksi jaringan (tentu saja dapat juga menggunakan localhost). Ini adalah program atau proses daemon yang tidak bergantung pada aplikasi (mode Daemon).
Memcached menggunakan perpustakaan libevent untuk mengimplementasikan layanan koneksi jaringan dan secara teoritis dapat menangani jumlah koneksi yang tidak terbatas. Namun, tidak seperti Apache, Memcached lebih sering berorientasi pada koneksi berkelanjutan yang stabil, sehingga kemampuan konkurensi sebenarnya terbatas. Dalam keadaan konservatif, jumlah maksimum koneksi simultan untuk memcached adalah 200, yang terkait dengan kemampuan thread Linux. Nilai ini dapat disesuaikan. Untuk informasi tentang libevent, silakan merujuk ke dokumentasi yang relevan. Penggunaan memori memcached juga berbeda dengan APC. APC didasarkan pada memori bersama dan MMAP. Memcachd memiliki algoritma alokasi memori dan metode manajemennya sendiri. Ini tidak ada hubungannya dengan memori bersama dan tidak memiliki batasan pada memori bersama. Biasanya, setiap proses memcached dapat mengelola ruang memori 2GB semakin banyak ruang yang dibutuhkan, jumlah proses dapat ditingkatkan.
◎Pada kesempatan apa Memcached cocok?
Dalam banyak kasus, memcached telah disalahgunakan, yang tentu saja menimbulkan keluhan terhadapnya. Saya sering melihat orang memposting di forum, mirip dengan "cara meningkatkan efisiensi", dan balasannya adalah "gunakan memcached". Adapun cara menggunakannya, di mana menggunakannya, dan untuk apa, tidak ada kalimatnya. Memcached bukanlah obat mujarab, juga tidak cocok untuk semua situasi.
Memcached adalah sistem cache objek memori "terdistribusi". Artinya, untuk aplikasi yang tidak perlu "didistribusikan", tidak perlu dibagikan, atau cukup kecil untuk hanya memiliki satu server, memcached tidak akan melakukannya. membawa manfaat apa pun. Sebaliknya juga memperlambat efisiensi sistem karena koneksi jaringan juga memerlukan sumber daya, bahkan koneksi lokal UNIX. Data pengujian saya sebelumnya menunjukkan bahwa kecepatan baca dan tulis lokal memcached puluhan kali lebih lambat dibandingkan array memori PHP langsung, sedangkan metode APC dan memori bersama mirip dengan array langsung. Terlihat jika hanya cache tingkat lokal, penggunaan memcached sangat tidak ekonomis.
Memcached sering digunakan sebagai cache front-end database. Karena penguraian SQL, operasi disk, dan overhead lainnya jauh lebih sedikit daripada database, dan menggunakan memori untuk mengelola data, ini dapat memberikan kinerja yang lebih baik daripada membaca database secara langsung. Dalam sistem besar, sangat sulit untuk mengakses data yang sama Seringkali, memcached dapat sangat mengurangi tekanan database dan meningkatkan efisiensi eksekusi sistem. Selain itu, memcached sering digunakan sebagai media penyimpanan untuk berbagi data antar server. Misalnya, data yang menyimpan status sistem masuk tunggal dalam sistem SSO dapat disimpan dalam memcached dan dibagikan oleh beberapa aplikasi.
Perlu diperhatikan bahwa memcached menggunakan memori untuk mengelola data, sehingga bersifat volatil. Ketika server di-restart atau proses memcached dihentikan, data akan hilang, sehingga memcached tidak dapat digunakan untuk menyimpan data. Banyak orang yang salah paham bahwa kinerja memcached sangat bagus, sama baiknya dengan perbandingan antara memori dan hard disk. Faktanya, memcached tidak akan mendapatkan ratusan atau ribuan peningkatan kecepatan membaca dan menulis dengan menggunakan memori koneksi, yang berkaitan dengan penggunaan memori. Dibandingkan dengan sistem database disk, keuntungannya adalah sangat "ringan". Karena tidak ada overhead yang berlebihan dan metode membaca dan menulis langsung, dapat dengan mudah menangani jumlah yang sangat besar pertukaran data, sehingga sering kali terdapat dua bandwidth jaringan gigabit. Semuanya terisi penuh, dan proses memcache itu sendiri tidak menghabiskan banyak sumber daya CPU.
◎Cara kerja Memcached
Pada bagian berikut, sebaiknya pembaca menyiapkan salinan kode sumber memcached.
Memcached adalah program layanan jaringan tradisional. Jika parameter -d digunakan saat memulai, maka akan dijalankan sebagai proses daemon. Proses pembuatan daemon diselesaikan oleh daemon.c. Program ini hanya memiliki satu fungsi daemon, yang sangat sederhana (jika tidak ada instruksi khusus yang diberikan, kode harus tunduk pada 1.2.1):
KODE:[Salin ke papan klip]#include <fcntl.h>
#termasuk <stdlib.h>
#termasuk <unistd.h>
int
daemon(nochdir, noclose)
int nochdir, noclose;
{
int fd;
saklar (garpu()) {
kasus-1:
kembali (-1);
kasus 0:
merusak;
bawaan:
_keluar(0);
}
jika (setsid() == -1)
kembali (-1);
jika (!nochdir)
(batal)chdir("/");
jika (!noclose && (fd = buka("/dev/null", O_RDWR, 0)) != -1) {
(batal)dup2(fd, STDIN_FILENO);
(batal)dup2(fd, STDOUT_FILENO);
(batal)dup2(fd, STDERR_FILENO);
jika (fd > STDERR_FILENO)
(batal)tutup(fd);
}
kembali (0);
}
Setelah fungsi ini membagi seluruh proses, proses induk keluar, lalu memindahkan STDIN, STDOUT, dan STDERR ke perangkat kosong, dan daemon berhasil dibuat.
Proses startup Memcached sendiri adalah sebagai berikut pada fungsi utama memcached.c:
1. Panggil settings_init() untuk mengatur parameter inisialisasi.
2. Baca parameter dari perintah startup untuk menetapkan nilai pengaturan
3. Tetapkan parameter LIMIT
4. Mulai pemantauan soket jaringan (jika ada jalur non-soket) (mode UDP didukung setelah 1.2)
5. Periksa identitas pengguna (Memcached tidak mengizinkan identitas root dimulai)
6. Jika jalur soket ada, buka koneksi lokal UNIX (pipa Sock)
7. Jika dimulai dalam mode -d, buat proses daemon (panggil fungsi daemon seperti di atas)
8. Inisialisasi item, acara, informasi status, hash, koneksi, lempengan
9. Jika pengelolaan diterapkan dalam pengaturan, buatlah array bucket
10. Periksa apakah halaman memori perlu dikunci
11. Inisialisasi sinyal, koneksi, hapus antrian
12. Jika dalam mode daemon, proses ID proses
13. Acara dimulai, proses startup berakhir, dan fungsi utama memasuki loop.
Dalam mode daemon, karena stderr telah diarahkan ke lubang hitam, tidak ada pesan kesalahan yang terlihat selama eksekusi yang akan diumpankan kembali.
Fungsi loop utama memcached.c adalah drive_machine. Parameter yang masuk adalah penunjuk struktur yang menunjuk ke koneksi saat ini, dan tindakan ditentukan berdasarkan status anggota negara.
Memcached menggunakan serangkaian protokol khusus untuk menyelesaikan pertukaran data. Dokumen protokolnya dapat merujuk ke: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
Di API, simbol baris baru disatukan sebagai rn
◎Metode manajemen memori Memcached
Memcached memiliki metode manajemen memori yang sangat unik untuk meningkatkan efisiensi, ia menggunakan metode pra-aplikasi dan pengelompokan untuk mengelola ruang memori, alih-alih malloc setiap kali perlu menulis data. . , bebaskan penunjuk saat menghapus data. Memcached menggunakan metode organisasi slab->chunk untuk mengelola memori.
Terdapat beberapa perbedaan dalam algoritma pembagian ruang pelat pada pelat.c di 1.1 dan 1.2, yang akan diperkenalkan secara terpisah nanti.
Slab dapat dipahami sebagai blok memori. Slab adalah unit terkecil untuk memcached untuk diterapkan pada memori pada satu waktu. Setiap lempengan dibagi menjadi beberapa bagian, dan setiap bagian menyimpan item. Setiap item juga berisi struktur item, kunci, dan nilai (perhatikan bahwa nilai dalam memcached hanya berupa string). Slab membentuk daftar tertaut menurut ID mereka sendiri, dan daftar tertaut ini digantung pada larik kelas lempengan menurut ID mereka. Keseluruhan struktur terlihat seperti larik dua dimensi. Panjang kelas pelat adalah 21 in 1.1 dan 200 in 1.2.
lempengan memiliki ukuran bongkahan awal, yaitu 1 byte dalam 1,1 dan 80 byte dalam 1,2. Terdapat nilai faktor dalam 1,2, yang defaultnya adalah 1,25.
Dalam 1,1, ukuran bongkahan dinyatakan sebagai ukuran awal * 2^n, n adalah classid, Yaitu: lempengan dengan id 0 mempunyai ukuran potongan 1 byte, lempengan dengan id 1 mempunyai ukuran potongan 2 byte, lempengan dengan id 2 mempunyai ukuran potongan 4 byte... lempengan dengan id 20 memiliki ukuran chunk sebesar 4 byte. Ukurannya 1 MB, yang berarti hanya ada satu chunk di dalam lempengan dengan ID 20:
KODE:[Salin ke clipboard]void slabs_init(batas_ukuran) {
ke dalam aku;
int ukuran=1;
mem_batas = batas;
untuk(i=0; i<=POWER_LARGEST; i++, ukuran*=2) {
kelas lempengan[i].ukuran = ukuran;
kelas lempengan[i].perslab = POWER_BLOCK / ukuran;
kelas lempengan[i].slot = 0;
kelas lempengan[i].sl_curr = kelas lempengan[i].sl_total = kelas lempengan[i].slab = 0;
kelas lempengan[i].end_page_ptr = 0;
kelas lempengan[i].end_page_free = 0;
kelas lempengan[i].slab_list = 0;
kelas lempengan[i].daftar_ukuran = 0;
kelas lempengan[i].pembunuhan = 0;
}
/* untuk test suite: memalsukan berapa banyak yang sudah kita malloc */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
jika (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* melakukan pra-alokasi lempengan secara default, kecuali variabel lingkungan
untuk pengujian disetel ke sesuatu yang bukan nol */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
lempengan_preallokasi(batas / POWER_BLOCK);
}
}
}
Dalam 1.2, ukuran potongan dinyatakan sebagai ukuran awal * f^n, f adalah faktor, yang didefinisikan dalam memcached.c, dan n diklasifikasikan. Pada saat yang sama, tidak semua 201 head perlu diinisialisasi karena faktornya adalah variabel dan inisialisasi hanya loop ke Ukuran yang dihitung mencapai setengah dari ukuran pelat, dan dimulai dari id1, yaitu: pelat dengan id 1, ukuran setiap potongan adalah 80 byte, pelat dengan id 2, ukuran setiap potongan adalah 80* f, id adalah 3 lempengan, setiap ukuran potongan adalah 80*f^2, dan ukuran inisialisasi memiliki nilai koreksi CHUNK_ALIGN_BYTES untuk memastikan penyelarasan n-byte (menjamin bahwa hasilnya merupakan kelipatan integral dari CHUNK_ALIGN_BYTES). Dengan cara ini, dalam keadaan standar, memcached1.2 akan diinisialisasi ke id40. Ukuran setiap bongkahan di lempengan ini adalah 504692, dan ada dua bongkahan di setiap lempengan. Terakhir, fungsi slab_init akan menambahkan id41 di bagian akhir, yang merupakan satu blok utuh, yaitu hanya ada satu potongan 1MB di lempengan ini:
KODE:[Salin ke clipboard]void slabs_init(batas ukuran_t, faktor ganda) {
int i = KEKUATAN_TERKECIL - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* Faktor 2.0 berarti menggunakan perilaku memcached default */
if (faktor == 2.0 && ukuran < 128)
ukuran = 128;
mem_limit = batas;
memset(slabclass, 0, sizeof(slabclass));
sementara (++i < POWER_LARGEST && ukuran <= POWER_BLOCK / 2) {
/* Pastikan item selalu selaras dengan n-byte */
jika (ukuran% CHUNK_ALIGN_BYTES)
ukuran += CHUNK_ALIGN_BYTES - (ukuran % CHUNK_ALIGN_BYTES)
;
kelas lempengan[i].perslab = POWER_BLOCK / kelas lempengan[i].ukuran;
ukuran *= faktor;
if (pengaturan.verbose > 1) {
fprintf(stderr, "kelas pelat %3d: ukuran potongan %6d perslab %5dn",
saya, kelas lempengan[i].ukuran, kelas lempengan[i].perslab);
}
}
kekuatan_terbesar = saya;
kelas lempengan[kekuatan_terbesar].ukuran = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* untuk rangkaian pengujian: memalsukan berapa banyak yang sudah kita malloc */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
jika (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)) {
lempengan_preallokasi(batas / POWER_BLOCK);
}
}
#endif
}
Seperti yang dapat dilihat dari penjelasan di atas, alokasi memori memcached bersifat mubazir. Jika sebuah lempengan tidak dapat dibagi berdasarkan ukuran bongkahan yang dimilikinya, ruang yang tersisa di ujung lempengan tersebut akan dibuang 1009384 Bytes, lempengan ini memiliki total 1 MB, sehingga terbuang sebanyak 39192 byte.
Memcached menggunakan metode ini untuk mengalokasikan memori agar dapat dengan cepat menemukan klasifikasi lempengan melalui panjang item. Ini sedikit mirip dengan hash, karena panjang item dapat dihitung Anda dapat mengetahui bahwa itu harus disimpan di lempengan id7, karena menurut metode perhitungan di atas, ukuran potongan id6 adalah 252 byte, ukuran potongan id7 adalah 316 byte, dan ukuran potongan id8 adalah 396 byte, yang berarti semua item 252 hingga Semua 316 byte harus disimpan di id7. Demikian pula, di 1.1, juga dapat dihitung antara 256 dan 512, dan harus ditempatkan di id9 dengan chunk_size 512 (sistem 32-bit).
Ketika Memcached diinisialisasi, lempengan akan diinisialisasi (seperti yang Anda lihat sebelumnya, slabs_init() dipanggil dalam fungsi utama). Ini akan memeriksa konstanta DONT_PREALLOC_SLABS di slabs_init(). Jika ini tidak ditentukan, berarti pelat diinisialisasi menggunakan memori yang telah dialokasikan sebelumnya, sehingga pelat dibuat untuk setiap ID di antara semua kelas pelat yang telah ditentukan. Artinya 1.2 akan mengalokasikan 41MB ruang pelat setelah memulai proses di lingkungan default. Selama proses ini, terjadi redundansi memori kedua dari memcached, karena ada kemungkinan id belum digunakan sama sekali, tetapi juga A. lempengan diterapkan secara default, dan setiap lempengan akan menggunakan memori 1MB.
Ketika lempengan sudah habis, dan item baru perlu dimasukkan dengan ID ini, lempengan tersebut akan diterapkan kembali untuk lempengan baru lempengan, ID yang sesuai Daftar tertaut lempengan akan bertambah. Daftar tertaut ini akan bertambah secara eksponensial. Dalam fungsi tumbuh_slab_list, panjang rantai ini berubah dari 1 menjadi 2, dari 2 menjadi 4, dari 4 menjadi 8...:
KODE:[Salin ke clipboard]static int grow_slab_list (unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->lempengan == p->ukuran_daftar) {
ukuran_t ukuran_baru = p->ukuran_daftar ?p->ukuran_daftar * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
jika (daftar_baru == 0) kembalikan 0;
p->ukuran_daftar = ukuran_baru;
p->slab_list = daftar_baru;
}
kembali 1;
}
Saat mencari item, digunakan fungsi slabs_clsid, parameter yang masuk adalah ukuran item dan nilai yang dikembalikan adalah classid. Dari proses ini terlihat bahwa redundansi memori ketiga dari memcached terjadi pada proses penyimpanan item. Item selalu lebih kecil dari atau sama dengan ukuran bongkahan. Jika item lebih kecil dari ukuran bongkahan, ruang akan terbuang lagi.
◎Algoritme NewHash Memcached
Penyimpanan item Memcached didasarkan pada tabel hash yang besar. Alamat sebenarnya adalah potongan offset di pelat, tetapi posisinya bergantung pada hasil hashing kunci, yang ditemukan di tabel_hash_utama. Semua operasi hash dan item didefinisikan di assoc.c dan items.c.
Memcached menggunakan algoritma yang disebut NewHash, yang sangat efektif dan efisien. Ada beberapa perbedaan antara NewHash di 1.1 dan 1.2. Metode implementasi utamanya masih sama. Fungsi hash di 1.2 telah diatur dan dioptimalkan, dan kemampuan adaptasinya lebih baik.
Referensi prototipe NewHash: http://burtleburtle.net/bob/hash/evahash.html . Matematikawan selalu sedikit aneh, haha~
Untuk memfasilitasi konversi, dua tipe data, u4 dan u1, didefinisikan. u4 adalah bilangan bulat panjang yang tidak ditandatangani, dan u1 adalah karakter yang tidak ditandatangani (0-255).
Untuk kode spesifik, silakan lihat paket kode sumber 1.1 dan 1.2.
Perhatikan panjang tabel hash di sini. Ada juga perbedaan antara 1.1 dan 1.2. Di 1.1, konstanta HASHPOWER didefinisikan sebagai 20, dan panjang tabel hash adalah ukuran hash (HASHPOWER), yaitu 4MB (ukuran hash adalah makro, menunjukkan bahwa 1 digeser ke kanan sebanyak n bit). Dalam 1.2 Ini adalah variabel 16, yaitu panjang tabel hash adalah 65536:
KODE:[Salin ke clipboard]typedef unsigned long int ub4; /* unsigned jumlah 4-byte */
typedef unsigned char ub1; /* jumlah 1 byte yang tidak ditandatangani */
#define hashsize(n) ((ub4)1<<(n))
#definisikan hashmask(n) (ukuran hash(n)-1)
Di assoc_init(), primary_hashtable akan diinisialisasi. Operasi hash yang sesuai meliputi: assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete(), sesuai dengan operasi baca dan tulis item. Diantaranya, assoc_find() adalah fungsi yang menemukan alamat item yang sesuai berdasarkan kunci dan panjang kunci (perhatikan bahwa dalam C, sering kali string dan panjang string diteruskan secara langsung pada saat yang sama alih-alih melakukan strlen di dalam fungsi ), dan yang dikembalikan adalah penunjuk struktur item, alamat datanya ada pada potongan di pelat.
items.c adalah program operasi untuk item data. Setiap item lengkap mencakup beberapa bagian, yang didefinisikan dalam item_make_header() sebagai:
kunci: kunci
nkey: panjang kunci
flags: flag yang ditentukan pengguna (sebenarnya, flag ini tidak diaktifkan di memcached)
nbytes: panjang nilai (termasuk simbol baris baru rn)
akhiran: akhiran Buffer
nsuffix: Panjang sufiks
item lengkap adalah panjang kunci + panjang nilai + panjang sufiks + ukuran struktur item (32 byte). Operasi item didasarkan pada panjang ini untuk menghitung klasifikasi pelat.
Setiap keranjang di tabel hash digantung dengan daftar tertaut ganda. Selama item_init(), tiga larik kepala, ekor, dan ukuran telah diinisialisasi ke 0. Ukuran ketiga larik ini adalah konstanta LARGEST_ID (defaultnya adalah 255, nilai ini memerlukan modifikasi dengan faktor), setiap kali item_assoc() dipanggil, ia akan mencoba mendapatkan potongan gratis dari lempengan terlebih dahulu. Jika tidak ada potongan yang tersedia, ia akan memindai daftar tertaut 50 kali untuk mendapatkan potongan itu dimulai dengan item LRU, putuskan tautannya, lalu masukkan item yang akan dimasukkan ke dalam daftar tertaut.
Perhatikan anggota item yang refcount. Setelah item dibatalkan tautannya, item tersebut hanya dihapus dari daftar tertaut. Item tersebut tidak langsung dibebaskan. Item tersebut hanya ditempatkan di antrian penghapusan (fungsi item_unlink_q()).
Item berhubungan dengan beberapa operasi baca dan tulis, termasuk operasi penghapusan, pembaruan, dan penggantian.
Fitur lain dari item adalah memiliki waktu kedaluwarsa, yang merupakan fitur memcached yang sangat berguna. Banyak aplikasi yang mengandalkan kedaluwarsa item memcached, seperti penyimpanan sesi, kunci operasi, dll. Fungsi item_flush_expired() memindai item dalam tabel dan melakukan operasi pembatalan tautan pada item yang kedaluwarsa. Tentu saja, ini hanyalah tindakan daur ulang. Faktanya, penilaian waktu juga diperlukan saat mendapatkan:
KODE:[Salin ke papan klip]/* item kedaluwarsa yang lebih baru daripada setelan terlama_yang aktif */
batal item_flush_expired() {
ke dalam aku;
item *ulangi, *berikutnya;
jika (! pengaturan.oldest_live)
kembali;
untuk (saya = 0; saya < ID_TERBESAR; saya++) {
/* LRU diurutkan dalam urutan waktu menurun, dan stempel waktu item
* tidak pernah lebih baru dari waktu akses terakhirnya, jadi kita hanya perlu berjalan kaki
* kembali hingga kita menemukan item yang lebih lama dari waktu_hidup terlama.
* Pemeriksaan terlama_live akan membuat item yang tersisa kedaluwarsa secara otomatis.
*/
untuk (iter = kepala[i]; iter != NULL; iter = selanjutnya) {
if (iter->waktu >= pengaturan.oldest_live) {
berikutnya = iter->berikutnya;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} kalau tidak {
/* Kita telah mencapai item lama yang pertama. Lanjutkan ke antrian berikutnya.
merusak;
}
}
}
}
KODE:[Salin ke clipboard]/* membungkus assoc_find yang melakukan logika kedaluwarsa/penghapusan yang lambat */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
barang *itu = assoc_find(kunci, nkey);
jika (hapus_terkunci) *hapus_terkunci = 0;
jika (itu && (itu->it_flags & ITEM_DELETED)) {
/* ditandai sebagai delete-locked. mari kita lihat apakah kondisinya seperti itu
sudah lewat jatuh tempo, dan delete_timer 5 detik belum
belum sampai... */
if (!item_delete_lock_over(itu)) {
if (hapus_terkunci) *hapus_terkunci = 1;
itu = 0;
}
}
if (itu && settings.oldest_live && settings.oldest_live <= waktu_saat ini &&
itu->waktu <= settings.oldest_live) {
item_unlink(itu);
itu = 0;
}
if (it && it->exptime && it->exptime <= waktu_saat ini) {
item_unlink(itu);
itu = 0;
}
mengembalikannya;
}
Metode manajemen memori Memcached sangat canggih dan efisien. Metode ini sangat mengurangi jumlah alokasi langsung memori sistem, mengurangi overhead fungsi dan kemungkinan fragmentasi memori. Meskipun metode ini akan menyebabkan beberapa pemborosan, pemborosan ini sepele dalam sistem besar aplikasi.
◎Metode perhitungan parameter teoritis Memcached
memiliki beberapa parameter yang mempengaruhi kerja memcached:
konstanta REALTIME_MAXDELTA 60*60*24*30
Waktu kedaluwarsa maksimum 30 hari
freetotal (=200) di conn_init()
Jumlah maksimum koneksi simultan
konstan KEY_MAX_LENGTH 250
panjang kunci maksimum
(=1,25)
faktor akan mempengaruhi ukuran langkah
pengaturan potongan.maxconns (=1024)
koneksi lunak maksimum.chunk_size
(=48)
Perkiraan panjang kunci+nilai secara konservatif, digunakan untuk menghasilkan panjang potongan (1.2) di id1. Panjang potongan id1 sama dengan nilai ini ditambah panjang struktur item (32), yang merupakan default 80 byte.
Konstan POWER_TERKECIL 1
Kelas minimum (1.2)
konstanta POWER_LARGEST 200
Kelas maksimum (1.2)
konstanta POWER_BLOCK 1048576
ukuran pelat default
CHUNK_ALIGN_BYTES (sizeof(void *))
Pastikan ukuran potongan adalah kelipatan bilangan bulat dari nilai ini untuk mencegah keluar batas (panjang void * berbeda pada sistem yang berbeda, yaitu 4 pada sistem standar 32-bit)
konstan ITEM_UPDATE_INTERVAL 60
interval penyegaran antrian
LARGEST_ID 255
Jumlah maksimum item dalam daftar tertaut (nilai ini tidak boleh lebih kecil dari kelas terbesar)
hashpower variabel (konstan HASHPOWER di 1.1)
Menentukan ukuran tabel hash
Berdasarkan pengaturan konten dan parameter yang diperkenalkan di atas, beberapa hasil dapat dihitung:
1. Tidak ada batas atas perangkat lunak untuk jumlah item yang dapat disimpan di memcached salah.
2. Dengan asumsi algoritma NewHash memiliki tumbukan yang seragam, jumlah siklus untuk menemukan suatu item adalah jumlah total item dibagi dengan ukuran tabel hash (ditentukan oleh hashpower), yang bersifat linier.
3. Memcached membatasi item maksimum yang dapat diterima hingga 1 MB, dan data yang lebih besar dari 1 MB akan diabaikan.
4. Pemanfaatan ruang Memcached memiliki hubungan yang baik dengan karakteristik data, dan juga terkait dengan konstanta DONT_PREALLOC_SLABS. Dalam kasus terburuk, 198 lempengan akan terbuang (semua item terkonsentrasi dalam satu lempengan, dan semua 199 ID dialokasikan sepenuhnya).
◎Optimasi panjang tetap Memcached
Berdasarkan uraian pada bagian di atas, saya memiliki pemahaman yang lebih mendalam tentang memcached. Itu hanya bisa dioptimalkan berdasarkan pemahaman yang mendalam.
Memcached sendiri dirancang untuk data dengan panjang variabel. Menurut karakteristik datanya, ini dapat dikatakan sebagai desain "berorientasi publik". Namun, sering kali, data kami tidak begitu "universal". distribusi tidak seragam, yaitu, panjang data terkonsentrasi di beberapa area (seperti menyimpan sesi pengguna); keadaan ekstrem lainnya adalah data dengan panjang yang sama (seperti nilai kunci dengan panjang tetap, sebagian besar data dengan panjang tetap terlihat di akses, statistik online, atau kunci eksekusi).
Di sini kita terutama mempelajari solusi optimasi untuk data dengan panjang tetap (1.2). Data dengan panjang variabel terdistribusi terpusat hanya untuk referensi dan mudah diimplementasikan.
Untuk menyelesaikan data dengan panjang tetap, hal pertama yang perlu diselesaikan adalah masalah alokasi pelat. Hal pertama yang perlu dipastikan adalah kita tidak memerlukan begitu banyak pelat dengan panjang potongan yang berbeda-beda sumber daya, yang terbaik adalah potongan dan item memiliki panjang yang sama, jadi pertama-tama hitung panjang item.
Telah ada algoritma untuk menghitung panjang item sebelumnya. Perlu diperhatikan bahwa selain panjang string, panjang struktur item harus ditambah 32 byte.
Misalkan kita telah menghitung bahwa kita perlu menyimpan 200 byte data dengan panjang yang sama.
Langkah selanjutnya adalah memodifikasi hubungan antara klasifikasi pelat dan panjang bongkahan. Dalam versi aslinya, ada hubungan yang sesuai antara panjang potongan dan classid. Sekarang jika semua potongan disetel ke 200 byte, maka hubungan ini tidak ada. Salah satu caranya adalah dengan hanya menggunakan ID tetap untuk seluruh struktur penyimpanan, yaitu hanya menggunakan 1 dari 199 slot. Dalam kondisi ini, DONT_PREALLOC_SLABS harus ditentukan untuk menghindari pemborosan pra-alokasi tambahan. Metode lain adalah dengan membuat hubungan hash untuk menentukan klasifikasi dari item. Anda tidak dapat menggunakan panjang sebagai kunci. Anda dapat menggunakan data variabel seperti hasil NewHash dari kunci, atau langsung melakukan hash berdasarkan kunci (the kunci data dengan panjang tetap juga harus memiliki panjang yang sama). Demi kesederhanaan di sini, kita memilih metode pertama. Kekurangan dari metode ini adalah hanya satu ID yang digunakan. Jika jumlah datanya sangat besar, rantai pelatnya akan sangat panjang (karena semua datanya penuh satu rantai). Biaya perjalanannya relatif tinggi.
Ketiga jenis redundansi ruang telah diperkenalkan sebelumnya. Menetapkan panjang bongkahan sama dengan panjang item memecahkan masalah sampah ruang yang pertama. Tidak mengajukan permohonan ruang terlebih dahulu akan menyelesaikan masalah sampah ruang yang kedua )? Untuk mengatasi hal ini, Anda perlu memodifikasi konstanta POWER_BLOCK sehingga ukuran masing-masing lempengan sama persis dengan kelipatan bilangan bulat dari panjang bongkahan, sehingga sebuah lempengan dapat dibagi menjadi n bongkahan. Nilai ini harus mendekati 1MB. Jika terlalu besar juga akan menyebabkan redundansi. Jika terlalu kecil akan menyebabkan terlalu banyak alokasi. Menurut panjang potongan 200, pilih 1000000 sebagai nilai POWER_BLOCK dengan cara ini, sebuah lempengan akan menjadi 1 juta byte, bukan 1048576. Ketiga masalah redundansi telah teratasi, dan pemanfaatan ruang akan meningkat pesat.
Ubah fungsi slabs_clsid sehingga langsung mengembalikan nilai tetap (misalnya 1):
KODE:[Salin ke clipboard]unsigned int slabs_clsid(ukuran_t ukuran) {
kembali 1;
}
Ubah fungsi slabs_init, hapus bagian yang berulang untuk membuat semua atribut classid, dan langsung tambahkan slabclass[1]:
KODE:[Salin ke clipboard]slabclass[1].size = 200; //200 byte per potongan
kelas lempengan[1].perslab = 5000; //1000000/200
◎Klien Memcached
Memcached adalah program layanan. Saat menggunakannya, Anda dapat terhubung ke server memcached sesuai dengan protokolnya, mengirim perintah ke proses layanan, dan kemudian mengoperasikan data di atas. Untuk kemudahan penggunaan, memcached memiliki banyak program klien yang tersedia, sesuai dengan berbagai bahasa, dan terdapat klien dalam berbagai bahasa. Yang berdasarkan bahasa C termasuk libmemcache dan APR_Memcache; yang berbasis Perl termasuk Cache::Memcached; ada juga dukungan untuk Python, Ruby, Java, C# dan bahasa lainnya. PHP memiliki klien terbanyak, tidak hanya dua ekstensi mcache dan PECL memcache, tetapi juga sejumlah besar kelas enkapsulasi yang ditulis oleh PHP. Berikut pengenalan cara menggunakan memcached di PHP:
Ekstensi mcache dienkapsulasi ulang berdasarkan libmemcache. . libmemcache belum merilis versi stabil. Versi saat ini adalah 1.4.0-rc2, yang dapat ditemukan di sini. Fitur yang sangat buruk dari libmemcache adalah ia menulis banyak pesan kesalahan ke stderr. Umumnya, ketika digunakan sebagai lib, stderr biasanya diarahkan ke tempat lain, seperti log kesalahan Apache, dan libmemcache akan bunuh diri, yang dapat menyebabkan Abnormal , namun performanya masih sangat bagus.
Ekstensi mcache terakhir diperbarui ke 1.2.0-beta10. Penulis mungkin mengundurkan diri. Dia tidak hanya berhenti memperbarui, tetapi dia juga tidak dapat membuka situs web (~_~). . Setelah dekompresi, cara instalasinya seperti biasa: phpize &configure & make & make install. Pastikan untuk menginstal libmemcache terlebih dahulu. Menggunakan ekstensi ini sederhana:
KODE:[Salin ke clipboard]<?php
$mc = memcache(); // Buat objek koneksi memcache. Perhatikan bahwa new tidak digunakan di sini!
$mc->add_server('localhost', 11211); // Tambahkan proses layanan
$mc->add_server('localhost', 11212); // Tambahkan proses layanan kedua
$mc->set('key1', 'Halo'); // Tulis kunci1 => Halo
$mc->set('key2', 'World', 10); // Tulis key2 => Dunia, kedaluwarsa dalam 10 detik
$mc->set('arr1', array('Halo', 'Dunia')); // Menulis sebuah array
$key1 = $mc->get('key1'); // Dapatkan nilai 'key1' dan tetapkan ke $key1
$key2 = $mc->get('key2'); // Dapatkan nilai 'key2' dan tetapkan ke $key2. Jika melebihi 10 detik, itu tidak akan tersedia.
$arr1 = $mc->get('arr1'); // Dapatkan array 'arr1'
$mc->hapus('arr1'); // Hapus 'arr1'
$mc->flush_all(); // Hapus semua data
$stats = $mc->stats(); // Dapatkan informasi server
var_dump($stats); // Informasi server adalah array
?>
Keuntungan dari ekstensi ini adalah dapat dengan mudah mengimplementasikan penyimpanan terdistribusi dan penyeimbangan beban, karena dapat menambahkan beberapa alamat layanan. Saat menyimpan data, data akan ditempatkan di server tertentu berdasarkan hasil hash . libmemcache mendukung metode hashing terpusat, termasuk hash CRC32, ELF, dan Perl.
PECL memcache merupakan extension yang dirilis oleh PECL. Versi terbaru adalah 2.1.0 yang bisa didapatkan di website pecl. Penggunaan ekstensi memcache dapat ditemukan di beberapa manual PHP yang lebih baru. Ini sangat mirip dengan mcache, sangat mirip:
KODE:[Salin ke clipboard]<?php
$memcache = Memcache baru;
$memcache->connect('localhost', 11211) or die ("Tidak dapat terhubung");
$version = $memcache->getVersion();
echo "Versi server: ".$version."n";
$tmp_object = stdClass baru;
$tmp_object->str_attr = 'uji';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) or die ("Gagal menyimpan data di server");
echo "Simpan data di cache (data akan habis masa berlakunya dalam 10 detik)n";
$get_result = $memcache->get('key');
echo "Data dari cache:n";
var_dump($get_result)
;
Ekstensi ini menggunakan aliran PHP untuk terhubung langsung ke server memcached dan mengirimkan perintah melalui soket. Ini tidak selengkap libmemcache, juga tidak mendukung operasi terdistribusi seperti add_server, tetapi karena tidak bergantung pada program eksternal lainnya, ia memiliki kompatibilitas yang lebih baik dan relatif stabil. Dari segi efisiensi, perbedaannya tidak terlalu besar.
Selain itu, ada banyak kelas PHP yang tersedia, seperti MemcacheClient.inc.php, dan banyak yang dapat ditemukan di phpclasses.org, umumnya merupakan enkapsulasi ulang API klien Perl dan digunakan dengan cara yang serupa.
◎BSM_Memcache
Dari perspektif klien C, APR_Memcache adalah program klien yang sangat matang dan stabil yang mendukung kunci thread dan operasi tingkat atom untuk memastikan stabilitas operasional. Namun, ini didasarkan pada APR (APR akan diperkenalkan di bagian terakhir) dan tidak memiliki berbagai aplikasi seperti libmemcache saat ini, tidak ada banyak program yang dikembangkan berdasarkan sebagian besar yang ada. karena tidak dapat berjalan di luar lingkungan April. Namun, APR dapat diinstal secara terpisah dari Apache.
BSM_MEMCACHE adalah ekstensi PHP berdasarkan apr_memcache yang saya kembangkan dalam proyek Bs.magic. Program ini sangat sederhana dan tidak melakukan terlalu banyak fungsi.
Berbeda dari ekstensi McAche yang mendukung penyimpanan terdistribusi multi-server, BSM_MEMCACHE mendukung beberapa kelompok server. , cadangan panas diimplementasikan. Tentu saja, biaya penerapan fungsi ini adalah pengorbanan kinerja. Biasanya Anda bisa mendapatkannya lain kali.
BSM_MEMCACHE hanya mendukung fungsi -fungsi ini:
Kode: [salin ke clipboard] 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}
};
Fungsi MC_ADD_GROUP mengembalikan integer (sebenarnya harus menjadi objek, saya malas ~ _ ~) sebagai ID grup. Addrort).
Kode: [Salin ke Clipboard]/**
*Tambahkan grup server
*/
Php_function (mc_add_group)
{
apr_int32_t group_id;
apr_status_t rv;
if (0! = zend_num_args ())
{
Salah_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
)
;
}
Kode: [Salin ke Clipboard]/**
* Tambahkan server ke dalam grup
*/
Php_function (mc_add_server)
{
apr_status_t rv;
apr_int32_t group_id;
G ganda;
char *srv_str;
int srv_str_l;
if (2! = zend_num_args ())
{
Salah_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g, & srv_str, & srv_str_l) == kegagalan)
{
Return_false;
}
group_id = (apr_int32_t) g
;
{
Return_false;
}
char *host, *lingkup;
port apr_port_t;
rv = apr_parse_addr_port (& host, & scope, & port, srv_str, p);
if (apr_success == rv)
{
// Buat objek server ini
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;
}
// Tambahkan server
=
apr_memcache_add_server (mc_groups [group_id], st);
{
Return_true;
}
}
}
Return_false;
}
Saat mengatur dan menghapus data, loop melalui semua grup:
Kode: [Salin ke Clipboard]/**
* Simpan item ke semua grup
*/
Php_function (mc_set)
{
char *kunci, *nilai;
int key_l, value_l;
TTL ganda = 0;
ganda set_ct = 0;
if (2! = zend_num_args ())
{
Salah_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == kegagalan)
{
Return_false;
}
// Tulis data ke setiap objek
apr_int32_t i = 0;
if (ttl <0)
{
ttl = 0;
}
apr_status_t rv;
untuk (i = 0; i <max_group; i ++)
{
if (0 == is_validate_group (i))
{
// tulislah!
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);
}
Di MC_GET, Anda pertama -tama memilih secara acak grup dan kemudian mulai polling dari grup ini:
Kode: [Salin ke Clipboard]/**
* Ambil item dari grup acak
*/
Php_function (mc_get)
{
char *key, *value = null;
int key_l;
apr_size_t value_l;
if (1! = zend_num_args ())
{
Salah_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == kegagalan)
{
Return_mull ();
}
// Saya akan mencoba ...
// baca acak
apr_int32_t Curr_group_id = random_group ();
apr_int32_t i = 0;
apr_int32_t coba = 0;
bendera apr_uint32_t;
apr_memcache_t *oper;
apr_status_t rv;
untuk (i = 0; i <max_group; i ++)
{
coba = i + curr_group_id;
coba = coba % max_group;
if (0 == is_validate_group (coba))
{
// Dapatkan nilai
oper = mc_groups [coba];
rv = apr_memcache_getp (mc_groups [coba], p, (const char *) kunci, & value, & value_l, 0);
if (apr_success == rv)
{
Return_string (nilai, 1);
}
}
}
Return_false;
}
Kode: [Salin ke Clipboard]/**
* ID grup acak
* Untuk mc_get ()
*/
apr_int32_t random_group ()
{
TV Struct Timeval;
struct timezone TZ;
int
usec
;
}
Penggunaan BSM_MemCache mirip dengan klien lain:
Kode: [Salin ke Clipboard] <? PHP
$ g1 = mc_add_group (); // tambahkan grup pertama
$ g2 = mc_add_group (); // Tambahkan grup kedua
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'); // Baca data
mc_del ('Key');
mc_shutdown ();
?>
Informasi yang relevan tentang apr_memcache dapat ditemukan di sini, dan bsm_memcache dapat diunduh dari situs ini.
◎ PENDAHULUAN LINGKUNGAN APR
Nama lengkap APR: Apache Runtime Portable. Ini adalah seperangkat perpustakaan bahasa lintas-platform C yang dibuat dan dikelola oleh Apache Software Foundation. Diekstraksi dari Apache httpd1.x dan tidak tergantung pada httpd. APR menyediakan banyak antarmuka API yang nyaman untuk digunakan, termasuk fungsi praktis seperti kumpulan memori, operasi string, jaringan, array, tabel hash, dll. Mengembangkan modul APACHE2 membutuhkan paparan ke banyak fungsi APR.
◎ Postscript
Ini adalah artikel terakhir saya di tahun Bingxu dari Kalender Lunar (tahun kelahiran saya). Karena memcached memiliki banyak konotasi, harus ada banyak kelalaian dan kesalahan dalam kompilasi tergesa -gesa. Terima kasih kepada Sina.com untuk memberikan peluang penelitian dan kolega di departemen atas bantuan mereka.
NP02-13-2007