آشنایی با مفاهیم اصلی Redis

Familiarity with the Main Concepts of Redis

27 خرداد 1400
redis: آشنایی با مفاهیم اصلی Redis

در این مقاله می خواهیم با مفاهیم کلی redis آشنا بشویم بنابراین متوجه باشید که این یک دوره آموزشی برای پایگاه داده Redis نیست گرچه چند مثال را نیز همراه با مفاهیم اصلی حل خواهیم کرد.

ویژگی های redis

redis معمولا با جمله in-memory key-value store شناخته می شود و معنی آن این است که redis یک پایگاه داده است که به جای هارد دیسک، روی مموری (RAM سیستم) سوار می شود و داده ها را به صورت جفت های  key-value ذخیره می کند، دقیقا مانند اشیاء در جاوا اسکریپت یا دیکشنری ها در پایتون. احتمالا شما نیز متوجه شده اید که ذخیره داده ها به صورت جفت های key-value به معنی عدم وجود schema است. در این سیستم key از نوع رشته و value از هر نوعی است که شما دوست داشته باشید؛ عدد، رشته، JSON و الی آخر.

مسئله اینجاست که تقریبا تمام پایگاه های داده های مشهور مانند MySQL و MongoDB داده ها را در هارد دیسک ذخیره می کنند اما redis اطلاعات را در RAM سیستم شما ذخیره می کند که مشکلات خاص خودش را دارد اما مزیت هایی نیز دارد. چه مشکلاتی؟ اولا مموری سیستم محدود است (سرور شما نهایتا چند گیگابایت RAM دارد) بنابراین چطور می توانیم داده های پایگاه داده را روی آن ذخیره کنیم؟ ثانیا ما می دانیم که داده های RAM ثابت یا persistent نیست و با ریستارت شدن سیستم/سرور تمام داده های آن حذف می شود! سوما اگر قرار بر ذخیره داده ها در مموری است، چرا داده ها را درون متغیر های اسکریپت خود ذخیره نکنیم!؟ چرا نیاز به یک پایگاه داده جداگانه داریم؟ در ادامه در این باره بحث خواهیم کرد.

یکی دیگر از ویژگی های redis این است که به صورت پیش فرض single threaded است، یعنی دستورات در یک thread و به صورت ترتیبی (یکی پس از دیگری) اجرا می شوند. البته اگر به سراغ قابلیت durability بروید یک thread دیگر برای آن کار نیز در نظر گرفته می شود.

مفهوم persistence در redis

redis در ابتدا کاملا in-memory بود، یعنی تمام داده ها درون مموری سیستم قرار می گرفتند و با یک ریستارت ساده همه چیز حذف می شد. به همین دلیل معمولا به redis به چشم یک پایگاه داده نگاه نمی کردند بلکه redis را به عنوان یک لایه کش روی پایگاه داده فعلی خود می دیدند تا زمانی که قابلیت durability توسط تیم redis معرفی شد. منظور ما از لایه کش این است که کوئری های پرتکرار به سمت پایگاه داده را به همراه نتایجشان در redis می گذاریم تا برای دریافت و ارسال آن ها از redis استفاده کنیم. دلیل پا فشاری بر redis نیز این است که سرعت دسترسی به داده از روی هارد دیسک و RAM متفاوت است تا حدی که زمان انتظار برای دریافت داده از redis معمولا کمتر از واحد میلی ثانیه است در حالی که چنین سرعتی در هیچ پایگاه داده ای در تمام دنیا ممکن نیست. دلیل این مسئله این است که ماهیت هارد دیسک با RAM تفاوت دارد و حتی دیسک های NVME هیچ گاه نمی توانند به سرعت RAM برسند.

اما با گذشت چند وقت تیم توسعه redis قابلیت durability یا persistence را نیز معرفی کرند که داده ها را علاوه بر مموری در دیسک نیز ذخیره می کند. این قابلیت به دو قسمت اصلی تقسیم می شود:

قابلیت AOF: به این حالت Journaling نیز گفته می شود. AOF مخفف Append Only File می باشد. در این حالت گزارشی از تمام عملیات ها را در دیسک ذخیره می کنیم اما با داده های پایگاه داده کاری نداریم (به طور مثال کاربر X داده Y را ثبت کرد یا حذف کرد و الی آخر). از آنجایی که تمام دستورات انجام شده در پایگاه داده درون یک فایل ذخیره شده اند، می توانیم پایگاه داده را دوباره بازسازی کنیم. یعنی چه؟ یعنی اگر سرور ما ریستارت شده و داده هایمان از بین بروند، redis به صورت خودکار به دنبال فایل AOF می گردد و تمام دستوراتی را که قبلا در پایگاه داده اجرا شده بود را روی خودش اجرا می کند. با این کار دوباره همان پایگاه داده قبلی را خواهیم داشت. البته لازم به ذکر است که منظور من از «تمام دستورات» تمام دستوراتی است که داده های پایگاه داده را تغییر دهد. دستوراتی که برای خواندن داده استفاده می شوند درون AOF قرار نمی گیرند چرا که نقشی در تعیین ساختار پایگاه داده ندارند. همچنین برای انجام این کار یک thread دیگر را خواهیم داشت که وظیفه نوشتن این اطلاعات روی دیسک را بر عهده دارد.

قابلیت RDB و snapshot: کلمه RDB مخفف Redis Database File است. در این قابلیت redis هر چند ثانیه از داده های شما یک snapshot تهیه می کند، یعنی از تمام داده ها یک کپی می گیرد و آن را روی دیسک ذخیره می کند (این داده ها در یک فایلی باینری به نام dump.rdb ذخیره می شوند). به طور مثال به دستور زیر توجه کنید:

save 60 1000

اگر در تنظیمات redis خود چنین دستوری را داشته باشید هر 60 ثانیه داده ها snapshot می شوند البته به شرطی که حداقل 1000 داده (key-value) تغییر کرده باشند. طبیعتا شما می توانید این مقادیر را در فایل تنظیمات سرور redis تغییر بدهید. نحوه انجام عملیات snapshot بدین شکل است:

  • ابتدا redis داده ها را fork می کند بنابراین یک پروسه پدر و یک پروسه فرزند داریم (child and parent process).
  • پروسه فرزند شروع به نوشتن داده ها روی یک فایل RDB می کند.

زمانی که این فرآیند تمام شود، فایل RDB جدید جایگزین فایل RDB قبلی می شود.

هر دو روش AOF و RDB به صورت ناهمگام در پس زمینه انجام می شوند. هر کدام از این روش ها و قابلیت ها مزایا و معایب خودشان را دارند که برای جزئیات بیشتر می توانید به صفحه redis.io/topics/persistence مراجعه کنید.

مفهوم Publish and Subscribe

در redis می توانید یک کانال ایجاد کنید تا کلاینت به آن subscribe کند (آن را پیگیری کند). از آن به بعد در هر زمانی که داده هایی را درون این کانال منتشر (publish) کنید، کلاینت آن داده ها را دریافت می کند.

مفهوم replication یا clustering

در replication به شما اجازه داده می شود که داده های خود را در چندین node کپی کنید. replication در redis مانند replication در دیگر پایگاه های داده مانند MySQL یا MongoDB است و اختصاصی به redis ندارد. دلیل کپی کردن این داده ها در چندین node مختلف این است که کلاینت (کاربران) را بین این node ها تقسیم کنیم. با این کار همه داده های خود را درون یک node قرار نمی دهیم تا تمام کاربران از یک node داده ها را بخواهند و بدین شکل سرور را scale می کنیم. در این مدل یک master یا leader داریم که node اصلی برنامه است و معمولا عملیات write بر عهده اش می باشد و سپس چندین slave یا follower داریم که برای عملیات های read استفاده می شوند. این slave ها داده ها را از master دریافت می کنند و زمانی که کلایت ها بخواهند داده ای را بخوانند از slave ها می خوانند تا master درگیر نشود. بنابراین اگر بخواهیم این مفهوم را به طور خلاصه توضیح بدهیم می گوییم یک قسمت اصلی به نام master داریم که پایگاه داده اصلی ما است و سپس چندین slave را داریم که داده های درون master را کپی می کنند تا فرآیند دریافت اطلاعات (READ) بر عهده آن ها باشد و روی سرور اصلی فشار نیاوریم.

در clustering به جای اینکه بخواهیم داده هایمان را در چندین node کپی کنیم، آن ها را بین node های مختلف تقسیم می کنیم. به طور مثال اگر ۲۰ هزار key-value داشته باشیم، می توانیم ۴ سرور داشته باشیم که هر کدام ۵ هزار کلید را در خود جا بدهند.

در نهایت می توانیم clustering و replication را با یکدیگر ترکیب کنیم تا به جای یک master چندین master داشته باشیم به طوری که داده ها بین آن ها تقسیم شده باشد (clustering) و سپس هر master چندین slave داشته باشد که داده هایش را کپی می کنند. به این روش، روش hybrid می گوییم.

چند مثال از دستورات redis

در این قسمت می خواهیم کمی با پایگاه داده redis به صورت ساده کار کنیم تا روش کلی کار را درک کنید. در این قسمت چند عملیات ساده را انجام می دهیم:

  • ابتدا یک مقدار key-value را در پایگاه داده ثبت می کنیم.
  • یک مقدار key-value دیگر را با تاریخ انقضا ثبت می کنیم.
  • مقدار ثبت شده را دریافت می کنیم.
  • وجود یا عدم وجود یک کلید را بررسی می کنیم.
  • یک کلید را حذف می کنیم.
  • مقداری را به مقدار کلید قبلی اضافه می کنیم.
  • یک کانال publish and subscribe ایجاد می کنیم.

طبیعتا برای اجرای این دستورات باید redis را روی سیستم خود نصب کرده باشید تا با اجرای دستور redis-cli وارد سرور redis شویم. حالا به سراغ تمرین ها می رویم. برای ثبت یک مقدار جدید در redis از دستور set استفاده می کنیم که ساختار زیر را دارد:

SET KEY VALUE EXPIRE

به طور مثال اگر من بخواهم اسم خودم را در پایگاه داده ثبت کنم باید یک نام به عنوان key و سپس یک مقدار را برای value انتخاب کنم. مثال:

SET name Amir

دستور بالا دقیقا معادل دستور زیر است:

SET name "Amir"

چرا؟ به دلیل اینکه اگر نوع داده خاصی را برای value تعریف نکنید، مقدار آن به صورت پیش فرض از نوع string (رشته) خواهد بود. با اجرای این دستور مقدار OK را دریافت می کنید که یعنی همه چیز با موفقیت انجام شده است. حالا چطور می توانیم این نام را از پایگاه داده دریافت کنیم؟ برای انجام این کار از دستور get استفاده می کنیم:

GET name

نتیجه:

"Amir"

حالا اگر بخواهیم مقداری را ثبت کنیم که پس از مدتی منقضی شود چطور؟ redis این قابلیت را دارد که پس از مدت خاصی key-value های شما را به صورت خودکار از بین ببرد. برای انجام این کار دوباره به ساختار دستور SET نگاهی می اندازیم:

SET KEY VALUE EXPIRE

همانطور که می بینید انقضا (EXPIRE) آخرین پارامتر این دستور است بنابراین می توانیم به شکل زیر عمل کنیم:

SET some_name "Ahmad" EX 10

دستور EX همان EXPIRE است و عدد ۱۰ یعنی ۱۰ ثانیه دیگر این کلید را حذف کن. با اجرای این دستور مثل همیشه OK می گیرید و اگر قبل از ۱۰ ثانیه دستور get some_name را اجرا کنید Ahmad را دریافت می کنید، در صورتی که پس از ۱۰ ثانیه مقدار (nil) برایتان برگردانده می شود. nil به معنی عدم وجود یک مقدار است. به طور مثال اگر در حال حاضر دستور get asdiasdilasldij را اجرا کنیم باز هم nil می گیریم چرا که هنوز هیچ کلیدی به نام asdiasdilasldij در پایگاه داده ما وجود ندارد اما استفاده از این روش از نظر منطقی جالب نیست و بهتر است برای بررسی وجود یک کلید در پایگاه داده از دستور exists و به شکل زیر استفاده کنیم:

EXISTS randomdata

با اجرای این دستور نتیجه زیر را می گیریم:

(integer) 0

عدد صفر یعنی کلید randomdata وجود ندارد. حالا بیایید به دنبال کلیدی بگردیم که می دانیم وجود دارد:

EXISTS name

با اجرای این دستور مقدار زیر را دریافت می کنید:

(integer) 1

عدد یک یعنی این کلید (name) وجود دارد.

اعداد صفر و یک را به همیشه به خاطر داشته باشید چرا که این دو عدد در redis به ترتیب به معنی «عدم تایید» و «تایید» هستند. به طور مثال برای حذف کردن یک کلید می توانیم از دستور del استفاده کنیم:

del name

با اجرای این دستور نتیجه زیر را می گیریم:

(integer) 1

عدد ۱ در اینجا به معنی تایید عملیات حذف است (یعنی کلید name وجود داشته است و حالا حذف شده است). در صورتی که دستور del را روی کلیدی اجرا کنید که وجود ندارد، به جای 1، عدد 0 را دریافت می کنید.

حالا سوالی مطرح می شود: اگر کلیدی وجود داشته باشد و ما دوباره با دستور SET مقداری را روی آن تنظیم کنیم چه اتفاقی می افتد؟ برای پاسخ به این موضوع سه دستور زیر را پشت سر هم اجرا کنید:

SET name Amir

SET name Ahmad

get name

من در اینجا کلید name را تعریف کرده و مقدارش را روی Amir گذاشته ام، سپس دوباره دستور SET را صدا زده و مقدار name را روی Ahmad گذاشته ام. در نهایت با صدا زدن GET می توانیم مقدار name را دریافت کنیم که Ahmad است بنابراین متوجه می شویم که دستور SET مقدار قبلی را نادیده گرفته و مقدار جدیدی را ثبت می کند. با این حساب اگر بخواهیم بدون حذف کردن مقدار قبلی، مقدار جدیدی را به آن اضافه کنیم چه کاری می توان انجام داد؟ برای انجام این کار دستور append در اختیار ما است:

append name " Rezaei"

من در اینجا Rezaei را به Ahmad (مقدار فعلی name) اضافه کرده ام نه اینکه آن را جایگزین مقدار قبلی کنم. در نظر داشته باشید که قبل از Rezaei یک اسپیس را نیز قرار داده ام تا Ahmad به آن نچسبد. با اجرای دستور بالا عدد ۱۲ را می گیرد که طول رشته name است. طبیعتا اگر به جای Rezaei چیز دیگری را به name اضافه کرده باشید، طول رشته آن تغییر پیدا کرده و به جای ۱۲ عدد دیگری را می گیرید. حالا می توانیم با اجرای دستور get name مقدار جدید name را ببینیم:

"Ahmad Rezaei"

همانطور که می بینید رشته بالا ۱۲ کاراکتر است (باید اسپیس را نیز حساب کنید). در ضمن اگر مقداری وجود نداشته باشد (مثلا کلید asduahsdkuhas) اما دستور appened را روی آن اجرا کنید، append دقیقا مانند SET عمل کرده و یک مقدار جدید را برای این کلید در نظر می گیرد.

در نهایت مبحث publish and subscribe را داریم. در ابتدا باید با مفهوم کانال یا channel آشنا شوید. زمانی که بحث از channel است، منظورمان محیطی ویژه و خاص از پایگاه داده است که داده های درون آن برایمان اهمیت خاصی دارند. ما می توانیم به این کانال خاص subscribe کنیم (ایجاد اشتراک) و سپس از تمام به روز رسانی های آن مطلع شویم. به طور مثال:

subscribe newPost

با اجرای این دستور به کانال جدیدی به نام newPost ثبت نام (subscribe) کرده ایم. کد بالا پس از اجرا نتیجه زیر را برایتان برمی گرداند:

Reading messages... (press Ctrl-C to quit)

1) "subscribe"

2) "newPost"

3) (integer) 1

به عبارتی شما وارد حالتی شده اید که اجازه اجرای دستورات دیگری را ندارید. در این حالت هیچ دستوری کار نمی کند مگر آنکه با کلید های Ctrl + C از آن خارج شوید. چرا؟ زمانی که ما به کانالی subscribe می کنیم، منتظر داده ها از سمت آن کانال باقی می مانیم. برای اینکه بتوانم این موضوع را به شما نشان دهم باید بدون بستن این ترمینال، یک پنجره ترمینال دیگر را باز کنید و با دستور redis-cli به redis متصل شوید. حالا در این ترمینال جدید دستور زیر را اجرا می کنیم:

publish newPost "THIS IS A NEW UPDATE"

با اجرای این دستور عدد ۱ را می گیریم که به معنی موفقیت آمیز بودن عملیات است. برای انتشار داده ای جدید در آن کانال خاص باید از دستور publish استفاده کرده و سپس نام کانال را بیاوریم. در نهایت هر داده ای را که می خواهیم انتشار می دهیم که در اینجا یک رشته ساده است. در صورتی که دستور publish را برای کانالی به کار ببرید که وجود ندارد، به جای عدد 1، عدد صفر را دریافت می کنید.

حالا به ترمینال قبلی برگردید. در این ترمینال باید نتیجه زیر را مشاهده کنید:

127.0.0.1:6379> subscribe newPost

Reading messages... (press Ctrl-C to quit)

1) "subscribe"

2) "newPost"

3) (integer) 1

1) "message"

2) "newPost"

3) "THIS IS A NEW UPDATE"

همانطور که می بینید پیام جدیدی را در این قسمت دریافت کرده ایم. نهایتا می توانید با دستور UNSUBSCRIBE ثبت نام خود از یک کانال خاص را حذف کنید. مثال:

unsubscribe newPost

در ضمن اگر نام کانال را به unsubscribe پاس ندهید، از تمام کانال ها خارج می شوید. باز هم لازم به ذکر است که این مقاله فقط جهت آشنایی شما با مفاهیم ساده Redis بود و اگر می خواهید redis را یاد بگیرید، دستورات بسیار بیشتری وجود دارد که باید آن ها را مطالعه کنید.

نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.