>کوئراییون همواره به ابزارها، متریکها و روشهای مختلفشان برای انجام دادن کارها معروفاند...
**آقا ممد** *(Aghaaaaaaa Mmd)* از **خفنترینها** و از **نکتهسنجترین** و **ریزبینترین** آدمها در مجموعه **کوئراست**! آقا ممد از **متریک** *(Metric)* **عجیبی** در **سنجش کیفیت درسنامهها** و **سوالات** مسابقات **کوئرا** استفاده میکند. **متن درسنامهها و سوالات کوئرا** که همگی از [**زبان نشانهگذاری مارکداون** *(Markdown)*](https://en.wikipedia.org/wiki/Markdown) استفاده میکنند و فایلهای آنها دارای **پسوند** `.md` هستند، همگی شامل **تعدادی بلوک متنی** و **تعدادی تصویر میباشند.**
از نظر آقا ممد تمامی **محتویات** این درسنامهها و سوالات، **تنها** به **دو دسته** تقسیمبندی میشوند، **تصاویر** و **بقیه**! به مثال زیر توجه کنید. **تصاویر** در **زبان نشانهگذاری مارکداون** به شکل زیر تعریف میشوند:
```markdown quera.md md

```
مثال زیر نمایش **تصویر همین سوال** در فرمت **مارکداون** است:
```markdown quera.md md

```

یکی از **متریکهایی** که آقا ممد از آن برای سنجیدن **کیفیت** درسنامهها و سوالات استفاده میکند، متریک **سیاهی** *(Blackness)* **فایل نهایی** `.md` آنها است. **متریک سیاهی ممد** به این صورت عمل میکند که او پس از دیدن **فایلهای مارکداونی** هر کدام از **درسنامهها** و **سوالات** کوئرا، **تصاویر** آنها را شناسایی کرده و دستهبندی گفته شده *(یعنی دستهبندی تصاویر و بقیه)* را روی **محتویات** فایلهای `.md` **اعمال** میکند. او سپس **محتویات** بین **هر دو عکس متوالی** *(یا یک عکس با شروع و پایان فایل)* را به عنوان یک **بلوک متنی** در نظر گرفته و **تعداد کاراکترهای آن** را به صورت دستی **میشمارد**! در صورتی که **تعداد** کاراکترهای **حداقل یک بلوک متنی** در فایل `.md` شامل **اکیداً بیشتر** از **۲۰۰ کاراکتر** باشد، **آن درسنامه یا سوال** نوشته شده **سیاه** *(Black)* است! سیاهی فایلهای مارکداونی باعث **کاهش خوانایی** و در نتیجه **کاهش درک کاربران** از محتویات گفته شده در آنها میشود.
از آنجایی که به لطف **آلیشا** *(AliSha)* اسکواد لید کوئرا، تعداد کالجها و کانتستهای کوئرا به صورت روزافزونی **در حال افزایش** است، آقا ممد که **نمیتواند** به صورت دستی **سیاهی** همهی آنها را بررسی کند، از شما میخواهد تا [**بش اسکریپتی** *(Bash Script)*](https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29) بنویسید که با **خواندن محتویات فایل** `quera.md` که فایل یکی از درسنامهها با **زبان نشانهگذاری مارکداون** است، **متریک سیاهی** را بسنجید و در صورتی که محتویات آن درسنامه **سیاه** بود، عبارت `YES` و در غیر این صورت `NO` را نمایش دهید.
# **ورودی اسکریپت**
**ورودی اسکریپت،** فایل `quera.md` است که **فایلی با زبان نشانهگذاری مارکداون** شامل تعدادی **تصویر** و **بلوک متنی** است. توجه داشته باشید که سیستم داوری محتویات فایل `quera.md` را به صورت جدا با **استریم ریدایرکشن** و یا **پایپلاین کردن** به اسکریپت شما **ورودی نمیدهد** و اسکریپت نوشته شده در فایل `solution.sh` خود **به تنهایی** قرار است تا **خواندن از فایل** `quera.md` و **نمایش خروجی** را انجام دهد.
# **خروجی اسکریپت**
**فایل** `solution.sh` شما **توسط سیستم داوری** اجرا شده و با خواندن **فایل** `quera.md` و انجام پردازشها، **بررسی کند که آیا درسنامه سیاه است یا نه.** در صورت **سیاه بودن** درسنامه باید عبارت `YES` و **در غیر این صورت** عبارت `NO` را چاپ کنید. **همچنین توجه داشته باشید که سیستم داوری به بزرگی و کوچکی حروف حساس است.**
# مثال
## ورودی نمونه ۱
```markdown quera.md md
Intro.

This is a short block.

Another short one.

```
## خروجی نمونه ۱
```
NO
```
- ورودی بالا **یک نمونه از فایل** `quera.md` میباشد که شامل **تعدادی تصویر** و **بلوک متنی** است. از آنجایی که **هیچ کدام** از بلوکهای متنی دارای **بیشتر** از ۲۰۰ کاراکتر **نمیباشند،** درسنامه سیاه نیست و عبارت `NO` چاپ خواهد شد.
## ورودی نمونه ۲
```markdown quera.md md

Short block one.

Short block two.

Still short.

We’re not yet over 200 characters.

This is the long block that should trigger the YES. It's designed to be well over 200 characters. Let’s keep adding more words, more phrases, more padding, and more redundancy until we are sure that we’ve gone beyond the threshold. That’s the goal of this test case. Padding complete.

Short again.

Final short block.
```
## خروجی نمونه ۲
```
YES
```
- ورودی بالا **یک نمونه دیگر از فایل** `quera.md` میباشد که شامل **تعدادی تصویر** و **بلوک متنی** است. از آنجایی که **تعداد کاراکترهای** بلوک متنی پنجم **اکیداٌ بیشتر** از ۲۰۰ کاراکتر میباشد پس آن بلوک باعث ایجاد **سیاهی** و تبدیل درسنامه به یک درسنامه **سیاه** میشود، پس **عبارت** `YES` چاپ خواهد شد.
# آنچه باید آپلود کنید
+ **توجه:** شما باید موارد خواسته شده در سوال را **تنها** در قالب **فایل** `solution.sh` پیادهسازی کرده و **ارسال** کنید.
+ **توجه:** فایل `solution.sh` توسط سیستم داوری اجرا میشود و **نیازی به نوشتن دستور یا اسکریپت دیگری برای اجرای این فایل نیست.**
+ **توجه:** سیستم داوری محتویات فایل `quera.md` را به صورت جدا با **استریم ریدایرکشن** و یا **پایپلاین کردن** به اسکریپت شما **ورودی نمیدهد** و اسکریپت نوشته شده در فایل `solution.sh` خود **به تنهایی** قرار است تا **خواندن از فایل** `quera.md` و **نمایش خروجی** را انجام دهد.
> **همواره** در مسابقاتی که توسط **مجموعه کوئرا** برگزار میشوند، تعدادی از کاربران به منظور **فعالیتهای توطئهگرانه،** **تقلب** و **بر هم زدن نظم** مسابقات، **رفتارهای عجیبی** انجام میدهند! برخی از آنها با ساخت **چنین حسابکاربری در کوئرا با یک نام یکسان** و یا **شرکت در چند مسابقه به صورت همزمان،** دست به **توطئهگری در کوئرا** میزنند...
ایرادگیر سری جدید کداستار، **آقای هاشیمی** *(MR. Hashimi)* از **مشروطهچیان** و **توطئهگران** مجموعه میهمن، برای برگزاری این سری مسابقات از کوئرا درخواستهای عجیبی دارد. از درخواستهای عجیب او برگزاری مسابقات مهندسی نرمافزار و فرانتاند در یک زمان به صورت مشترک بود. از نظر **آقای هاشیمی** برگزاری مسابقات **مهندسی نرمافزار** و **فرانتاند** در **یک زمان** میتواند به **شناسایی متقلبان** مسابقات کمک بسیاری کند.

او از **کوئرا** میخواهد تا پس از برگزاری این دو مسابقه و خروجی گرفتن نتایج **مسابقه مهندسی نرمافزار** در **فایل** `software.csv` و نتایج **مسابقه فرانتاند** در **فایل** `front.csv` ، آنها را **تشابهسنجی ابلفضلی** کند! به مثال زیر از **فایل نتایج** این دو مسابقه توجه کنید:
```csv front.csv csv
Ali Pishgrad,99
Sara Momeni,50
```
```csv software.csv
Zahra Ahmadi,80
Zahra Ahmadi,70
Zahra Ahmadi,90
```
- **فایل نتایج مسابقات کوئرا**، به صورت فایلهایی با **پسوند** `.csv` میباشد که هر سطر آن به ترتیب شامل **نام** و **رتبه** فرد شرکتکننده هستند.
**تشابهسنج ابلفضلی** کوئرا با بررسی هر کدام از **فایلهای نتایج،** یعنی `front.csv` و `software.csv`، **ابتدا** سطرهایی از نتایج که **نام افراد شرکتکننده** آنها **تکرار** شده است را شناسایی کرده و **صرفا نتیجهای را نگهداری کند که رتبه بهتری را در مسابقه کسب کرده است!** این **تشابهسنجی** در هر کدام از فایلها باعث میشود تا **در نهایت نام و رتبه تمام شرکتکنندگان متمایز گردد.**
سپس این **تشابهسنج** باید با **بررسی نام شرکتکنندهها در هر دو فایل** `front.csv` و `software.csv` در صورتی که **نامی** را مشاهده کند که **در هر دو مسابقه** شرکت کرده است، از آنجایی که **هر دو مسابقه در یک زمان** برگزار شدهاند و یک فرد **نمیتواند** به صورت **همزمان** در هر دوی آنها شرکت کند، **آن فرد باید به عنوان متقلب شناسایی شده** و در **سمت راست نامش یک** `*` برای نشان دادن **متقلب بودن فرد** قرار گیرد.
**در نهایت** نتایج هر کدام از مسابقات **مهندسی نرمافزار** و **فرانتاند** باید **بر اساس رتبه به صورت صعودی مرتبسازی شوند** تا **آقای هاشیمی** بتواند تصمیمگیری درستی در مورد پذیرش افراد **در بوتکمپهای کداستار** انجام دهد. از آنجایی که همه در کوئرا سخت مشغول برگزاری مجموعه مسابقات کداستار هستند، مسئولیت نوشتن بش اسکریپت تشابهسنج ابلفضلی کوئرا در این سوال به شما واگذار شده است!
# **ورودی اسکریپت**
ورودی اسکریپت، **دو فایل** `software.csv` و `front.csv` است که هر کدام شامل تعدادی سطر **به ترتیب** شامل **نام** و **رتبه** فرد شرکتکننده میباشند، که با `,` از هم **جدا** شدهاند. ممکن است برخی از نامها در فایلهای ورودی تکراری باشند که باید توسط تشابهسنج ابلفضلی شناسایی شوند. همچنین تضمین میشود که تمامی رتبههای مسابقات متمایز هستند و رتبه تکراری در نتایج وجود ندارد.
توجه داشته باشید که **سیستم داوری** محتویات فایل `front.csv`و `software.csv` را به صورت جدا با **استریم ریدایرکشن** و یا **پایپلاین کردن** به اسکریپت شما **ورودی نمیدهد** و اسکریپت نوشته شده در فایل `solution.sh` خود **به تنهایی** قرار است تا **خواندن از فایل** `front.csv`و `software.csv` و **نمایش خروجی** را انجام دهد.
# **خروجی اسکریپت**
فایل `solution.sh` شما **توسط سیستم داوری** اجرا شده و با خواندن **فایلهای** `software.csv` و `front.csv` و **تشابهسنجی ابلفضلی** و انجام پردازش بر روی آن باید **هر کدام از نتایج** را به صورت گفته شده *(به صورت نتایج متمایز و صعودی بر اساس رتبه که افراد متقلب در آنها شناسایی شدهاند)* **دوباره در هر کدام از همان فایلها** ذخیرهسازی کند. **توجه داشته باشید** که سیستم داوری **نسبت به بزرگی و کوچکی حروف نام افراد شرکتکننده، موقعیت قرارگیری علامت** `*` **برای افراد متقلب و نبود فاصلههای اضافهتر در فایلهای پردازش شده و نهایی** `software.csv` و `front.csv` **حساس** است.
# مثال
## ورودی نمونه ۱
```csv front.csv csv
Ali Pishgrad,99
Sara Momeni,50
```
```csv software.csv
Mohammad Rezaei,40
Zahra Ahmadi,70
```
## خروجی نمونه ۱
```csv front.csv csv
Sara Momeni,50
Ali Pishgrad,99
```
```csv software.csv
Mohammad Rezaei,40
Zahra Ahmadi,70
```
- **خروجیهای** بالا، یک نمونه از **فایلهای** `front.csv` و `software.csv` میباشند. این **دو فایل** پس از انجام **تشابهسنجی ابلفضلی** و **مرتبسازی** به صورت بالا تبدیل شدهاند. **توجه داشته باشید** که از آنجایی که **نام هیچ فردی** در **هر دو فایل** نتیجه مسابقات مهندسی نرمافزار و فرانتاند **نبوده است**، فردی **به عنوان متقلب شناسایی نشده است.**
## ورودی نمونه ۲
```csv front.csv csv
Zahra Ahmadi,80
Zahra Ahmadi,70
Zahra Ahmadi,90
```
```csv software.csv
Zahra Ahmadi,85
```
## خروجی نمونه ۲
```csv front.csv csv
Zahra Ahmadi*,70
```
```csv software.csv
Zahra Ahmadi*,85
```
- **خروجیهای** بالا، یک نمونه از **فایلهای** `front.csv` و `software.csv` میباشند. این **دو فایل** پس از انجام **تشابهسنجی ابلفضلی** و **مرتبسازی** به صورت بالا تبدیل شدهاند. **فایل** `front.csv` پس از **تشابهسنجی**، از آنجایی که **سه شرکتکننده** با نامهای **یکسان** `Zahra Ahmadi` دارد، **تنها بهترین نتیجه** برای این نام که **رتبه ۷۰** میباشد **باقی مانده** و **سایر نتایج** از فایل `front.csv` **حذف** شدهاند. همچنین از آنجایی که **این نام** علاوه بر **فایل** `front.csv` در فایل `software.csv` نیز دیده شده و **این فرد در هر دو مسابقه** که **به صورت همزمان** برگزار شدهاند، شرکت کرده است، پس او به عنوان **فرد متقلب**، شناسایی شده و کنار نامش پس از **تشابهسنجی ابلفضلی** علامت `*` قرار گرفته است. آقای هاشیمی او را از شرکت در بوتکمپهای کداستار منع خواهد کرد!
# آنچه باید آپلود کنید
+ **توجه:** شما باید موارد خواسته شده در سوال را **تنها** در قالب **فایل** `solution.sh` پیادهسازی کرده و **ارسال** کنید.
+ **توجه:** خروجی پاسخ شما باید **عینا مطابق عبارت خواسته شده باشد** *(یعنی از لحاظ بزرگی و کوچکی حروف و علائم نگارشی و موقعیت قرار گیری* `*` *در کنار نام باید بدون تغییرات اضافی باشد)*
+ **توجه:** فایل `solution.sh` توسط سیستم داوری اجرا میشود و **نیازی به نوشتن دستور یا اسکریپت دیگری برای اجرای این فایل نیست.**
+ **توجه:** نتایج پس از **تشابهسنجی ابلفضلی** باید در **همان فایلهای** `front.csv` و `software.csv` **بازنویسی** شوند.
+ **توجه:** **سیستم داوری** محتویات فایل `front.csv`و `software.csv` را به صورت جدا با **استریم ریدایرکشن** و یا **پایپلاین کردن** به اسکریپت شما **ورودی نمیدهد** و اسکریپت نوشته شده در فایل `solution.sh` خود **به تنهایی** قرار است تا **خواندن از فایل** `front.csv`و `software.csv` و **نمایش خروجی** را انجام دهد.
سلیب پس از برگزاری موفقیتآمیز [**مجموعه مسابقات کداستار**](https://quera.org/events/codestar-0403)، دوباره با **ممزواد** *(Mamzavad)،* **عنصر فتنهی کوئرا** به مشکلات جدیای بر میخورد و از آنجایی که **قدرت ممزواد** در کوئرا **خیلی بیشتر از سلیب** است، او ناچار میشود تا برای اتمام درگیری در کوئرا، پس از اتمام مسابقات کداستار به **مزرعه پنبهاش** پناه برده و **به کشت و برداشت پنبه مشغول شود!**
از آنجایی که سلیب علاوه بر فردی **بسیار بانظم** بودن، به **اسکریپتهای** *Lua* و همچنین **ردیس کلاستر** *(Redis Cluster)* بسیار علاقهمند است، از شما که تنها فرد مورد اعتماد باقی مانده برای او هستید، میخواهد تا مدیریت **کشت** و **برداشت پنبه** در مزرعهاش را با استفاده از **دو** *Endpoint*، `/get` و `/set` که توسط یک [**سرویس** *OpenResty*](https://openresty.org/en/) و [**اسکریپتهای** *Lua*](https://www.lua.org/) پیادهسازی خواهند شد، مدیریت کنید. وظیفه شما در این سوال پیادهسازی بخشی از این سیستم مدیریت پنبههای سلیب با استفاده از **اسکریپتهای** *Lua*، کانفیگ **فایل** `nginx.conf` و نوشتن **فایل** `docker-compose.yml` برای **راهاندازی سرویسهای مورد نیاز مزرعه پنبه** میباشد.

**پنبههای سلیب** به صورت مقادیر **کلید-مقدار** هستند که شما باید آنها را در **یک ردیس کلاستر با ۳ عدد نود** *(Node)* ذخیرهسازی کنید. سلیب میخواهد تا برای **بالانس لود در نودهای ردیسی،** هر بار **کلیدهای پنبههایش** را با استفاده از **الگوریتم هش** *FNV-1a* هش کرده و به یکی از **شاردهای** `shard1`, `shard2`, `shard3` اختصاص دهد که **هر کدام از ۳ نود ردیسی** *(یا Replica آنها)* میباشد.
# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/85281/download_problem_initial_project/291403/) کلیک کنید.
<details class="yellow">
<summary>
**ساختار فایلها**
</summary>
```
├── <mark class="red" title="این فایل باید پیادهسازی شود">docker-compose.yml</mark>
├── nginx
│ ├── <mark class="red" title="این پوشه باید شامل کدهای منطقی این سوال باشد">lua</mark>
│ └── <mark class="red" title="این فایل باید پیادهسازی شود">nginx.conf</mark>
```
</details>
برای راهاندازی پروژه، از یک **فایل** `docker-compose.yml` استفاده خواهد شد، که شامل چند سرویس مختلف برای **مدیریت دادههای پنبههای مزرعه سلیب** با استفاده از *Redis* و *OpenResty* میباشد.
# **جزئیات پروژه**
<details class="red">
<summary>
**پیادهسازی فایل کانفیگ** `nginx.conf`**و اسکریپتهای** *Lua*، **منطق مزرعه پنبه!**
</summary>
در این سوال، شما وظیفه دارید تا با استفاده از *OpenResty،* که یک سیستم مبتنی بر *Nginx* با پشتیبانی از *Lua* پیادهسازی کنید. این سیستم باید شامل **پیکربندی** کامل سرور *Nginx* در مسیر `nginx/nginx.conf` و همچنین **اسکریپتهای** *Lua* در مسیر پوشه `nginx/lua` باشد که **منطق مورد نیاز برای مدیریت درخواستها و ارتباط با کلاستر ردیسی را اجرا میکنند.**
+ لازم است فایل پیکربندی `nginx.conf` را بهصورت کامل بنویسید. این فایل باید شامل تنظیمات مربوط به بارگذاری *Lua،* تعریف مسیرهای مورد نیاز (مانند `/get` و `/set`)، و پیکربندی منابع اشتراکی (مثل `lua_shared_dict`) باشد. توجه داشته باشید که **تنها دو مسیر** *(Route)* `get` و `set` مجاز خواهند بود و *Nginx* باید به ازای باقی مسیرها خطای `404` نمایش دهد.
+ **اسکریپتهای** مربوط به منطق **برنامهنویسی** باید در مسیر `nginx/lua/` قرار داده شوند. **این اسکریپتها ممکن است شامل ماژولهایی برای موارد زیر باشند، البته توجه داشته باشید که مدیریت چگونگی نحوه پیادهسازی اجزای مختلف بر عهدهی شما میباشد:**
+ **اتصال به کلاستر ردیس** *(شامل مدیریت Shard، Fallback و Health check)*
+ **اعمال محدودیت نرخ** *(Rate limiting)*
+ **خواندن و نوشتن کلیدها با استفاده از** *GET* **و** *SET*
+ **تولید پاسخ در قالب** *JSON* **و هندل کردن خطاها**
ساختار پروژه شما باید به گونهای باشد که امکان **اجرای کامل سیستم** با راهاندازی *Nginx* فراهم شود. **توجه داشته باشید** که **تمامی منطق سمت سرور** باید به کمک *Lua* و از طریق *OpenResty* اجرا شود و **اجازه استفاده از سرورهای خارجی یا زبانهای دیگر وجود ندارد.**
**در ادامه به بررسی هر کدام از موارد ذکر شده به صورت مفصلتر همراه با مثال خواهیم پرداخت.**
</details>
<details class="blue">
<summary>
**پیادهسازی فایل** `docker-compose.yml`، **زیرساخت مزرعه پنبه!**
</summary>
## **سرویس** *OpenResty*
با استفاده از **ایمیج** زیر یک **سرویس** با نام `openresty` ایجاد کنید:
```
registry.gitlab.com/qio/standard/openresty:1.19.9.1-5-alpine-fat
```
+ این **سرویس** برای اجرای **کدهای** *Lua* و مدیریت درخواستها با استفاده از *Nginx* و *OpenResty* استفاده خواهد شد.
+ **پورت** `8080` باید از **میزبان** به **کانتینر** متصل شود.
+ **فایل کانفیگ** *Nginx* در **مسیر** `nginx/nginx.conf` و اسکریپتهای پیادهسازی شده در **پوشه** `nginx/lua` را به **مسیرهای مرتبط** در **سرویس** `openresty` متصل کنید.
+ **این سرویس به تمامی نودهای کلاستر ردیسی وابسته است!**
## **سرویسهای** *Redis*
**مجموعاً ۶ سرویس** *Redis* در این پروژه تعریف شدهاند:
+ **سه نود** *Redis* **اصلی** (`redis1`, `redis2`, `redis3`)
+ **سه نود** *Redis* به عنوان *Replica* (`redis1r`, `redis2r`, `redis3r`)
همهی این سرویسها از ایمیج زیر استفاده میکنند:
```
registry.gitlab.com/qio/standard/redis:latest
```
+ این سرویسها برای **ذخیرهسازی دادهها** در یک **ساختار توزیعشده** *Redis* استفاده میشوند.
# **نکاتی در مورد پیادهسازی فایل** `docker-compose.yml`
+ **توجه:** شما اجازهی *build* کردن یک ایمیج جدید **نخواهید داشت** و باید از ایمیجهای استاندارد کوئرا استفاده کنید.
+ **توجه:** **نام کانتینرها و سرویسها** باید **دقیقا** عبارات گفته شده در متن سوال باشند.
+ **توجه:** سیستم داوری کوئرا بهصورت خودکار فایل `docker-compose.yml` را با کامند `up` اجرا میکند. **شما نیازی به کد یا اسکریپتی برای اجرای این کار ندارید.**
+ **توجه:** ورژن `docker-compose.yml` باید `3.3` باشد.
+ **توجه:** تنها فایل `docker-compose.yml` شما در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود. همچنین شما باید حتما **تنها** از **ایمیجهای معرفی شده در متن سوال** استفاده کنید. **استفاده از سایر ایمیجها باعث عدم داوری ارسال شما خواهد شد.**
``` yaml docker-compose.yml docker
version: "3.3"
# Do not forget that the only available
# openresty image is accessible with the following url:
# registry.gitlab.com/qio/standard/openresty:1.19.9.1-5-alpine-fat
# Do not forget that the only available
# redis image is accessible with the following url:
# registry.gitlab.com/qio/standard/redis:latest
```
</details>
<details class="green">
<summary>
**پیادهسازی** *Setter* **برای کشت پنبه!**
</summary>
در این بخش، **سلیب** قرار است تا امکان **ذخیره مقدار** *(Value)* **جدید پنبه** برای یک **کلید** *(Key)* دلخواه را فراهم کند. ابتدا بررسی میشود آیا **کلید** (`key`) و **مقدار** (`value`) در *Query String* درخواست وجود دارد یا خیر. سپس با استفاده از تابع هش گفته شده، مشخص میشود کلید در کدام **شارد** *(Shard)* **کلاستر ردیسی** *(Redis Cluster)* قرار دارد. شارد هر **کلید** $key$ به این صورت مشخص خواهد شد:
$$FNV-1a(key) mod 3 + 1$$
- **طبق رابطه بالا**، پس از محاسبه هش کلید `key` **باقیمانده آن را بر ۳** محاسبه کرده و **یکی شیفت میدهیم** تا **شارد** *(Shard)* نهاییاش محاسبه شود.
پس از محاسبه شارد کلید مورد نظر و افزودن مقدار آن کلید در نود مورد مشخص شده (یا Replica ی آن)، در صورتی که این ذخیرهسازی موفق بود، خروجی باید یک *JSON* شامل وضعیت (`status`) **موفقیت** (`OK`)، **کلید** (`key`)، **مقدار** (`value`) ذخیرهشده و **نام شارد** (`shard`) باشد.
اگر **مقدار** یا **کلید** ارسال نشده باشند، باید **خطایی** با کد `400` به همراه پیام مناسب نمایش داده شود. اگر عملیات `SET` شکست بخورد *(مثلاً ردیسی در دسترس نباشد)،* باید **خطای** `500` **به همراه پیام مناسب بازگردانده میشود.**
همانطور که پیشتر توضیح داده شد، در مسیر `/set`، **درخواستهایی** برای **ثبت مقدار** (`value`) برای **یک کلید** (`key`) خاص ارسال میشوند. این بخش به طور کلی شامل مراحل زیر است:
## **مراحل پیادهسازی**
- **بررسی وجود** `key` و `value` در *Query String.*
- محاسبه هش *FNV-1a* روی `key` برای تعیین **شارد کلید** (یکی از `shard1`, `shard2`, `shard3`).
- تلاش برای اتصال به **نود اصلی** *(Primary Node)* ردیس مربوطه.
- تلاش برای **اتصال** به نود *Replica* ی شارد مورد نظر در صورت در دسترس نبود **نود اصلی**، مثلا در صورتی که **شارد کلیدی** `shard1` باشد، **سرویس** *OpenResty* باید سعی کند تا به **نود ردیسی** `redis1` متصل شود و **در صورت شکست**، به **نود** *Replica* ی آن یعنی `redis1r` متصل شود.
- ذخیرهسازی **مقدار** مورد نظر برای **کلید** در **دیتابیس ردیسی**
- بازگرداندن *JSON* با کد `200` شامل اطلاعات `key`, `value`, `shard`, `status`.
## مثالها
### **درخواستهای موفق برای ثبت مقدار جدید**
```bash terminal terminal
curl "http://localhost:8080/set?key=key3&value=value3"
```
```json output json
{
"key": "key3",
"value": "value3",
"status": "OK",
"shard": "shard1"
}
```
```bash terminal terminal
curl "http://localhost:8080/set?key=key1&value=value1"
```
```json output json
{
"key": "key2",
"value": "value2",
"status": "OK",
"shard": "shard2"
}
```
```bash terminal terminal
curl "http://localhost:8080/set?key=key6&value=value6"
```
```json output json
{
"key": "key6",
"value": "value6",
"status": "OK",
"shard": "shard3"
}
```
### **درخواستهای ناموفق برای ثبت مقدار جدید، بدون کلید و مقدار**
```bash terminal terminal
curl "http://localhost:8080/set?value=blue"
```
```json output json
{
"error": "Missing 'key'"
}
```
```bash terminal terminal
curl "http://localhost:8080/set?key=color"
```
```json output json
{
"error": "Missing 'value'"
}
```
### **درخواست ناموفق برای ثبت مقدار جدید، در دسترس نبودن همزمان نود اصلی و** *Replica*
```bash terminal terminal
curl "http://localhost:8080/set?key=color&value=green"
```
```json output json
{
"error": "Redis unavailable"
}
```
| توضیحات | کد وضعیت | خروجی نمونه |
|----------------------------------|----------|------------------------------------------------------------------------------|
| **ارسال درست مقدار و کلید** | `200` | `{ "key": "key3", "value": "value3", "status": "OK", "shard": "shard1" }` |
| **ارسال بدون** key | `400` | `{ "error": "Missing 'key'" }` |
| **ارسال بدون** value | `400` | `{ "error": "Missing 'value'" }` |
| **عدم دسترسی به نود اصلی** و `replica` | `500` | `{ "error": "Redis unavailable" }` |
</details>
<details class="yellow">
<summary>
**پیادهسازی** *Getter* **برای برداشت پنبه!**
</summary>
در این بخش، **سلیب** باید قادر باشد **مقدار ذخیرهشدهای** را با استفاده از **کلید** (`key`) از کلاستر **ردیس** دریافت کند. ابتدا بررسی میشود آیا **کلید** در *Query String* درخواست وجود دارد یا خیر. سپس با استفاده از تابع هش گفته شده، مشخص میشود این کلید در کدام **شارد** *(Shard)* از **کلاستر ردیسی** قرار دارد. برای تعیین شارد، از رابطه زیر استفاده میشود:
$$FNV-1a(key) mod 3 + 1$$
- پس از محاسبه هش `key`، **باقیمانده تقسیم بر ۳** محاسبه شده و با **۱ جمع** میشود تا شماره شارد (`shard1`, `shard2`, `shard3`) مشخص گردد.
- تلاش میشود تا به **نود اصلی** *(Primary)* **شارد** مربوطه متصل شویم.
- اگر نود اصلی در دسترس **نبود**، اتصال به *Replica* صورت میگیرد.
- سپس دستور `GET` روی کلید اجرا میشود.
- اگر **مقدار** یافت نشد (`null`)، فیلد `found` باید مقدار `false` داشته باشد.
- در صورت **موفقیت**، **خروجی شامل مقادیر** `key`, `value`, `found`, `shard` در **قالب** *JSON* و **کد وضعیت** `200` خواهد بود.
- در صورت **ارسال نشدن کلید**، **کد خطای** `400` با **پیام مناسب** بازگردانده میشود.
- در صورت **عدم در دسترس بودن هیچ نودی**، **کد** `500` به همراه **پیام مناسب** بازگردانده میشود.
## **مراحل پیادهسازی**
- **بررسی وجود** `key` در *Query String.*
- محاسبه **هش** *FNV-1a* روی `key` برای تعیین **شارد کلید**
- تلاش برای اتصال به **نود اصلی** شارد مربوطه.
- تلاش برای اتصال به *Replica* در صورت **عدم موفقیت** در اتصال به **نود اصلی.**
- **دریافت مقدار کلید از دیتابیس ردیسی**
- **بازگرداندن** نتیجه در قالب *JSON* همراه با **وضعیت** `200`.
## **مثالها**
### **درخواستهای موفق برای دریافت مقدار کلید**
```bash terminal terminal
curl "http://localhost:8080/get?key=key1"
```
```json output json
{
"key": "key1",
"found": true,
"value": "value1",
"shard": "shard2"
}
```
```bash terminal terminal
curl "http://localhost:8080/get?key=key6"
```
```json output json
{
"key": "key6",
"found": true,
"value": "value6",
"shard": "shard3"
}
```
### **درخواست ناموفق بدون کلید**
```bash terminal terminal
curl "http://localhost:8080/get"
```
```json output json
{
"error": "Missing 'key'"
}
```
### **درخواست برای کلید ناموجود**
```bash terminal terminal
curl "http://localhost:8080/get?key=unknown"
```
```json output json
{
"key": "unknown",
"found": false,
"value": null,
"shard": "shard2"
}
```
### **درخواست ناموفق به دلیل عدم دسترسی به ردیس**
```bash terminal terminal
curl "http://localhost:8080/get?key=key9"
```
```json output json
{
"error": "Redis unavailable"
}
```
| توضیحات | کد وضعیت | خروجی نمونه |
|----------------------------------|----------|------------------------------------------------------------------------------|
| **دریافت مقدار موجود** | `200` | `{ "key": "key1", "found": true, "value": "value1", "shard": "shard2" }` |
| **عدم ارسال کلید** | `400` | `{ "error": "Missing 'key'" }` |
| **دریافت کلیدی که وجود ندارد** | `200` | `{ "key": "unknown", "found": false, "value": null, "shard": "shard3" }` |
| **عدم دسترسی به نود اصلی** و `replica` | `500` | `{ "error": "Redis unavailable" }` |
</details>
<details class="blue">
<summary>
**پیادهسازی** *Rate Limiter*، **برای محافظت مزرعه!**
</summary>
برای محافظت از **منابع سیستم** در برابر حملات و بار بیش از حد، سلیب باید یک **محدودکننده نرخ درخواست** (*Rate Limiter*) بر پایهی **IP کلاینت** پیادهسازی کند.
## **مراحل پیادهسازی**
- در هر بازه **۶۰ ثانیهای**، **حداکثر ۶۰ درخواست** از یک *IP* **مجاز** است.
- **اگر تعداد خطاها از این حد بیشتر شود**، کاربر باید با **خطای** `429 Too Many Requests` مواجه شده و از ادامه فعالیت او برای **مدت زمان ۱۵ ثانیهای،** جلوگیری به عمل آید.
## مثالها
### **درخواست بیش از حد مجاز**
```bash terminal terminal
curl "http://localhost:8080/get?key=key1"
```
```json output json
{
"error": "Rate limit exceeded"
}
```
| توضیحات | کد وضعیت | خروجی نمونه |
|----------------------------------|----------|------------------------------------------------------------------------------|
| **درخواست در محدودۀ مجاز** | `200` | `{ "key": "test", "value": "val", "status": "OK", "shard": "shard1" }` |
| **عبور از حد مجاز درخواست** | `429` | `{ "error": "Rate limit exceeded" }` |
</details>
<details class="pink">
<summary>
**پیادهسازی** *Fallback*، **برای پشتیبانی مزرعه!**
</summary>
برای اطمینان از **پایداری بالا** و جلوگیری از قطع خدمات در زمان از کار افتادن برخی نودهای ردیسی، سلیب باید از **مکانیزم** *(Fallback)* استفاده کند.
* در زمان **پردازش درخواست**، اگر **نود اصلی** سالم باشد، به آن متصل میشویم. در غیر اینصورت، به **نود** *Replica* متصل میشویم.
* اگر **هیچکدام** در دسترس **نبودند**، پاسخ با **خطای** `500` و **پیام** `Redis unavailable` بازگردانده میشود.
## **مراحل پیادهسازی**
- **بررسی سلامت و دسترس پذیری نود اصلی**
- **اتصال** به **نود اصلی** در صورت سالم بودن
- **اتصال** به نود *Replica* در صورت ناسالم بودن نود اصلی.
- **در صورت در دسترس نبودن هر دو**، بازگرداندن **خطای** `500`.
## مثالها
### **عدم دسترسی به نود اصلی و Replica**
```bash terminal terminal
curl "http://localhost:8080/get?key=key4"
```
```json output json
{
"error": "Redis unavailable"
}
```
| توضیحات | کد وضعیت | خروجی نمونه |
|----------------------------------|----------|------------------------------------------------------------------------------|
| **نود اصلی سالم و قابل اتصال** | `200` | `{ "key": "key5", "found": true, "value": "value5", "shard": "shard3" }` |
| **اتصال مستقیم به** `replica` | `200` | `{ "key": "key6", "found": true, "value": "value6", "shard": "shard2" }` |
| **عدم دسترسی به هر دو نود** | `500` | `{ "error": "Redis unavailable" }` |
</details>
# آنچه باید آپلود کنید
+ **توجه**: پس از اعمال تغییرات، کل پروژه را _Zip_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.