- مقدمه
- ساختار کلی
- مراحل اجرا و نحوه کار کردن Memcached
- راه اندازی در سمت سرور
- راه اندازی در سمت کلاینت
- نوشتن یک تست
- مقایسه Redis و Memcached
- منابع
سیستم Memcached، یک سیستم ذخیرهسازی و کش کردن دیتا به صورت توزیع شده میباشد. با استفاده از Memecached میتوانیم با ذخیرهسازی دادهها در RAM، تعداد درخواست برای خواندن دیتا از پایگاهداده را کاهش دهیم. از Memcached میتوان بر روی سیستمعاملهای unix based و microsoft windows استفاده نمود. همچنین یک پروژه open-source میباشد که توسط Brad Fitzpatrick طراحی و سپس گسترش یافته است. شرکتهای بزرگی مانند Reddis, Twitter youtube و … از این سیستم استفاده کردهاند. در ادامه تلاش خواهیم کرد با معرفی ساختار این سیستم و روشهای استفاده کردن از آن، آشنا شویم.
معماری memcached به صورت کلاینت-سرور میباشد. در سمت سرور یک جدول هش بسیار بزرگ وجود دارد که در آن کلید اصلی میتواند تا ۲۵۰ بایت و مقدار متناظر میتواند تا حجمی برابر با ۱ مگابایت را اشغال کند. در نتیجه میتوان دادههای عددی تا دادههای حجیم تر مثل صفحات html را در آن ذخیرهسازیکنیم. همچنین سیستمطوری طراحی شده است که از طریق TCP و UDP قابل دسترس باشد. در نتیجه سرورها میتوانند ساختار توزیع شده (distributed) و یا مجزا (separated) داشته باشند. اگر حافظهی کش پر شود، سیستم به طور اتوماتیک دادهی جدید را جایگزین قدیمیترین داده میکند؛ در نتیجه باید از آن به عنوان یک نوع کش گذرا (transitory cache) نام برد.
به طور کلی میتوان معماری Memcached را به چهار قسمت اصلی تقسیم کرد:
- نرمافزار کاربر که به آن لیستی از سرورهای قابل دسترس داده میشود.
- یک الگوریتم هش که میتوان با استفاده از آن، سرور مدنظر برای دریافت داده را شناسایی کرد.
- سرور که در آن یک جدول هش بزرگ موجود است.
- LRU که با استفاده از آن تصمیمگرفته میشود که داده جدید جایگزین شود و یا از داده قبلی استفاده شود.
با توجه به ساختار سیستم، مراحل اجرا به صورت زیر میباشد:
- کاربر در ابتدا یک داده را جستوجو میکند و Memcached چک میکند که آیا دادهی مدنظر کش شده است یا خیر.
- اگر دادهی مدنظر در کش موجود بود، آن داده برگردانده میشود. در غیر این صورت، دادهی مدنظر در دیتابیس مورد جستوجو قرار میگیرد.
- اگر زمان منقضی شدن یک داده گذشته باشد، کش آپدیت میشود تا از تازه بودن دادههای در دسترس کاربر، اطمینان حاصل شود.
در ادامه میخواهیم یک حافظهی کش را با کمک Memcached ایجاد کنیم. دقت شود که تمامی دستورهای استفاده شده، بر روی سیستم لینوکس قابل اجرا میباشد.
برای نصب بر روی لینوکس (debian based system) میتوان از دستور زیر استفاده نمود.
sudo apt-get install memcached
پس از نصب، یک فال کانفیگ ایجاد میشود که میتوان با تغییر آن، نحوهی اجرا شدن Memcached را کنترل کرد. در ادامه به معرفی برخی از فلگهای موجود در کانفیگ میپردازیم.
- -m memory
- -p port
پورتی که برای کانکشنها، با TCP مورد استفاده قرار میگیرد. مقدار اولیه آن برابر 18080 میباشد.
- -U port
پورتی که برای کانکشنها، با UDP مورد استفاده قرار میگیرد. مقدار اولیه آن برابر 18080 میباشد.
- -c max_connections
حداکثر تعداد کانکشنهایی که میتواند همزمان رخ دهد. مقدار اولیه آن برابر 1024 میباشد.
- -t number_of_threads
تعداد تردهای مورد استفاده برای اجرا کردن درخواستها. مقدار اولیه آن برابر 4 میباشد.
برای آشنایی بیشتر با فلگهای موجود در کانفیگ میتوانید به رفرنس ۴ مراجعه کنید.
به کمک systemctl میتوانیم، دستورات اجرایی لازم را بنویسیم.
sudo systemctl start memcached
sudo systemctl stop memcached
sudo systemctl restart memcached
sudo systemctl status memcached
برای آنکه بتوانیم تحت ترمینال با Memcached ارتباط برقرار کنیم نیاز به کتابخانهی libmemcached-tools داریم که با استفاده از دستور زیر نصب میشود.
sudo apt-get install libmemcached-tools
همانطور که در بخش قبل دیدیم نیاز داریم تا یک نرمافزار سمت کلاینت داشته باشیم تا بتوانیم با سرور خود ارتباط برقرار کنیم. به طور مثال میتوان از Node js به همراه پکیج مربوط به Memcached که برای این آن طراحی شده است، استفاده نمود. در ادامه با استفاده از express سعی خواهیم کرد تا یک برنامهی کلاینت برای سرور خود بنویسیم.
لازم است که لیستی از سرورهای قابل دسترس را برای کلاینت مشخص کنیم. این کار را به کمک constructor کش ایجاد میکنیم.
const Memcached = require('memcached');
const memcached = new Memcached(serverLocations, options);
مقدار server location در صورتی که تنها یک سرور موجود باشد، میتواند string باشد و در مواردی که خوشهای از سرورها وجود دارد، میتوان از list و یا object تعریف کرد.
در آبجکت option میتوانیم بیشینه مقدار کلید و مقدار را مشخص کنیم. همچنین اندازه connection pool به همراه دیگر موارد به کمک option تعریف میشود.
const connection_string = 'localhost:11211'; // use for single server
const memcached = new Memcached(connection_string, {
maxKeySize: 250, // max size of a key in bytes
maxValues: 10000, // max size of the value in bytes
poolSize: 10, // number of connections to each memcached server
algorithm: 'md5', // hash algorithm to use
});
در ادامه به برخی از APIهای مهم میپردازیم
memcached.set(key, value, lifetime, callback);
با استفاده از تابع set میتوانیم یک کلید به همراه یک مقدار را به همراه مدت زمان expire شدن آن (به ثانیه) در کش ذخیره کنیم. همچنین از یک تابع callback در صورت رخ دادن error در هنگام ذخیرهسازی استفاده میشود.
به مثال زیر توجه کنید:
const data_chunk = {
"user_id": "123",
"user_name": "Yum",
"user_email": "Yum@sharif.ir"
}
const data_token = 'ABCDEF1234567890';
memcached.set(data_token, data_chunk, 20, function (err) {
if (err) console.error(err);
else console.log('Data is saved in memcached');
});
memcached.get(key, callback);
با استفاده از تابع get میتوانیم با داشتن کلید، به مقدار متناظر با آن کلید دست پیدا کنیم. همچنین از یک تابع callback در صورت رخ دادن error و همچنین اجرای فرآیندهای لازم بر روی دیتا دریافتی استفاده میشود.
به مثال زیر توجه کنید:
memcached.get(data_token, function (err, data) {
if (err) console.error(err);
else {
console.log('Data is retrieved from memcached');
console.log(data);
}
});
memcached.touch(key, lifetime, callback);
با استفاده از تابع touch میتوانیم با داشتن یک کلید، زمان قابل قبول بودن داده را تغییر دهیم. همچنین از یک تابع callback در صورت رخ دادن error در هنگام بهنگامسازی استفاده میشود.
به مثال زیر توجه کنید:
memcached.touch(data_token, 3, function (err) {
if (err) console.error(err);
else console.log('Data is touched in memcached');
});
حال میتوانیم با استفاده از این دستوارت، یک تست بنویسیم. فایل app.js را باز کنید و محتوای زیر را در آن قرار دهید. (از نصب کتابخانههای مورد نیاز اطمینان حاصل کنید!)
const express = require("express");
const app = express();
const port = Number(process.env.PORT || 5000);
const Memcached = require('memcached');
const connection_string = 'localhost:11211'; // use for single server
const memcached = new Memcached(connection_string, {
maxKeySize: 250, // max size of a key in bytes
maxValues: 10000, // max size of the value in bytes
poolSize: 10, // number of connections to each memcached server
algorithm: 'md5', // hash algorithm to use
});
// data we want to save in memcached
const data_chunk = {
"user_id": "123",
"user_name": "Yum",
"user_email": "Yum@sharif.ir"
}
// token for data
const data_token = 'ABCDEF1234567890';
// set data in memcached. expire time is 20 seconds
memcached.set(data_token, data_chunk, 20, function (err) {
console.log('\x1b[34m%s\x1b[0m', '\n**Data is saved in memcached');
if (err) console.error(err);
else console.log('Data is saved in memcached');
});
// try to get data after 1 second
setTimeout(function () {
console.log('\x1b[34m%s\x1b[0m', '\n**Get data from memcached after 1 second');
memcached.get(data_token, function (err, data) {
if (err) console.error(err);
else console.log(data);
});
}, 1000);
// try to touch data after 2 seconds.
// the new expiration time is 3 seconds (data is expired after 5 seconds)
setTimeout(function () {
console.log('\x1b[33m%s\x1b[0m', '\n**Touch data in memcached after 2 second');
memcached.touch(data_token, 3, function (err) {
if (err) console.error(err);
else console.log('Data is touched : new expiration time is 3 seconds');
});
}, 2000);
// try to get data after 6 seconds (note that data is expired after 5 seconds)
setTimeout(function () {
memcached.get(data_token, function (err, data) {
console.log('\x1b[31m%s\x1b[0m', '\n**Get data from memcached after 6 second');
if (err) console.error(err);
else {
if (!data) {
console.log('Data is expired');
}
}
});
}, 6000);
console.log("Listening on " + port + ", Web URL: http://localhost:" + port);
app.listen(port);
با استفاده از دستور زیر، تست را اجرا کنید:
node app.js
در صورت اجرای موفقیتآمیز، خروجی زیر را مشاهده خواهید کرد:
هر دوی این سیستمها با استفاده از ذخیرهسازی in-memory سرعت زیادی در دریافت داده دارند همچنین این امکان را میدهند تا داده را میان چنین node توزیع کرد که در صورت رشد دادهها بسیار کارآمد میباشد. بر خلاف Redis، در Memcached دادهساختارهای متنوعی وجود ندارد به عبارتی در Redis علاوه بر ذخیرهسازی رشته میتوانستیم انواع دادهساختارهای دیگر مانند list, set, bit array و... را ذخیرهسازی کنیم. سیستم Memcached دارای معماری multi threading میباشد در نتیجه میتوان با بهره بردن از هستههای پردازشی بیشتر، عملیاتهای بیشتری انجام دهیم. برخلاف Redis در سیستم Memcached نمیتوان Replication ایجاد نمود. همچنین عملیاتهای transaction نیز غیرقابل اجرا است.
تصویر زیر یک مقایسه کلی را از این دو مدل کش داده نشان میدهد