سلیب پس از برگزاری موفقیتآمیز [**مجموعه مسابقات کداستار**](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 آنها)* میباشد.
# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/problemset/assignments/4367/download_problem_initial_project/291526/) کلیک کنید.
<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_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.
مزرعه پنبه سلیب
سلیب پس از برگزاری موفقیتآمیز مجموعه مسابقات کداستار، دوباره با ممزواد (Mamzavad)، عنصر فتنهی کوئرا به مشکلات جدیای بر میخورد و از آنجایی که قدرت ممزواد در کوئرا خیلی بیشتر از سلیب است، او ناچار میشود تا برای اتمام درگیری در کوئرا، پس از اتمام مسابقات کداستار به مزرعه پنبهاش پناه برده و به کشت و برداشت پنبه مشغول شود!
از آنجایی که سلیب علاوه بر فردی بسیار بانظم بودن، به اسکریپتهای Lua و همچنین ردیس کلاستر (Redis Cluster) بسیار علاقهمند است، از شما که تنها فرد مورد اعتماد باقی مانده برای او هستید، میخواهد تا مدیریت کشت و برداشت پنبه در مزرعهاش را با استفاده از دو Endpoint، /get
و /set
که توسط یک سرویس OpenResty و اسکریپتهای Lua پیادهسازی خواهند شد، مدیریت کنید. وظیفه شما در این سوال پیادهسازی بخشی از این سیستم مدیریت پنبههای سلیب با استفاده از اسکریپتهای Lua، کانفیگ فایل nginx.conf
و نوشتن فایل docker-compose.yml
برای راهاندازی سرویسهای مورد نیاز مزرعه پنبه میباشد.

پنبههای سلیب به صورت مقادیر کلید-مقدار هستند که شما باید آنها را در یک ردیس کلاستر با ۳ عدد نود (Node) ذخیرهسازی کنید. سلیب میخواهد تا برای بالانس لود در نودهای ردیسی، هر بار کلیدهای پنبههایش را با استفاده از الگوریتم هش FNV-1a هش کرده و به یکی از شاردهای shard1
, shard2
, shard3
اختصاص دهد که هر کدام از ۳ نود ردیسی (یا Replica آنها) میباشد.
پروژه اولیه🔗
برای دانلود پروژهی اولیه روی این لینک کلیک کنید.
ساختار فایلها
برای راهاندازی پروژه، از یک فایل docker-compose.yml
استفاده خواهد شد، که شامل چند سرویس مختلف برای مدیریت دادههای پنبههای مزرعه سلیب با استفاده از Redis و OpenResty میباشد.
جزئیات پروژه🔗
پیادهسازی فایل کانفیگ nginx.conf
و اسکریپتهای Lua، منطق مزرعه پنبه!
در این سوال، شما وظیفه دارید تا با استفاده از 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 اجرا شود و اجازه استفاده از سرورهای خارجی یا زبانهای دیگر وجود ندارد.
در ادامه به بررسی هر کدام از موارد ذکر شده به صورت مفصلتر همراه با مثال خواهیم پرداخت.
پیادهسازی فایل docker-compose.yml
، زیرساخت مزرعه پنبه!
سرویس OpenResty🔗
با استفاده از ایمیج زیر یک سرویس با نام openresty
ایجاد کنید:
این سرویس برای اجرای کدهای Lua و مدیریت درخواستها با استفاده از Nginx و OpenResty استفاده خواهد شد.
پورت 8080
باید از میزبان به کانتینر متصل شود.
فایل کانفیگ Nginx در مسیر nginx/nginx.conf
و اسکریپتهای پیادهسازی شده در پوشه nginx/lua
را به مسیرهای مرتبط در سرویس openresty
متصل کنید.
این سرویس به تمامی نودهای کلاستر ردیسی وابسته است!
سرویسهای Redis🔗
مجموعاً ۶ سرویس Redis در این پروژه تعریف شدهاند:
سه نود Redis اصلی (redis1
, redis2
, redis3
)
سه نود Redis به عنوان Replica (redis1r
, redis2r
, redis3r
)
همهی این سرویسها از ایمیج زیر استفاده میکنند:
- این سرویسها برای ذخیرهسازی دادهها در یک ساختار توزیعشده Redis استفاده میشوند.
نکاتی در مورد پیادهسازی فایل docker-compose.yml
🔗
توجه: شما اجازهی build کردن یک ایمیج جدید نخواهید داشت و باید از ایمیجهای استاندارد کوئرا استفاده کنید.
توجه: نام کانتینرها و سرویسها باید دقیقا عبارات گفته شده در متن سوال باشند.
توجه: سیستم داوری کوئرا بهصورت خودکار فایل docker-compose.yml
را با کامند up
اجرا میکند. شما نیازی به کد یا اسکریپتی برای اجرای این کار ندارید.
توجه: ورژن docker-compose.yml
باید 3.3
باشد.
توجه: تنها فایل docker-compose.yml
شما در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایلها بیتاثیر خواهند بود. همچنین شما باید حتما تنها از ایمیجهای معرفی شده در متن سوال استفاده کنید. استفاده از سایر ایمیجها باعث عدم داوری ارسال شما خواهد شد.

docker-compose.yml
پیادهسازی Setter برای کشت پنبه!
در این بخش، سلیب قرار است تا امکان ذخیره مقدار (Value) جدید پنبه برای یک کلید (Key) دلخواه را فراهم کند. ابتدا بررسی میشود آیا کلید (key
) و مقدار (value
) در Query String درخواست وجود دارد یا خیر. سپس با استفاده از تابع هش گفته شده، مشخص میشود کلید در کدام شارد (Shard) کلاستر ردیسی (Redis Cluster) قرار دارد. شارد هر کلید key به این صورت مشخص خواهد شد:
FNV−1a(key)mod3+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
.
مثالها🔗
درخواستهای موفق برای ثبت مقدار جدید🔗

terminal

output

terminal

output

terminal

output
درخواستهای ناموفق برای ثبت مقدار جدید، بدون کلید و مقدار🔗

terminal

output

terminal

output
درخواست ناموفق برای ثبت مقدار جدید، در دسترس نبودن همزمان نود اصلی و Replica🔗

terminal

output
توضیحات |
کد وضعیت |
خروجی نمونه |
ارسال درست مقدار و کلید |
200 |
{ "key": "key3", "value": "value3", "status": "OK", "shard": "shard1" } |
ارسال بدون key |
400 |
{ "error": "Missing 'key'" } |
ارسال بدون value |
400 |
{ "error": "Missing 'value'" } |
عدم دسترسی به نود اصلی و replica |
500 |
{ "error": "Redis unavailable" } |
پیادهسازی Getter برای برداشت پنبه!
در این بخش، سلیب باید قادر باشد مقدار ذخیرهشدهای را با استفاده از کلید (key
) از کلاستر ردیس دریافت کند. ابتدا بررسی میشود آیا کلید در Query String درخواست وجود دارد یا خیر. سپس با استفاده از تابع هش گفته شده، مشخص میشود این کلید در کدام شارد (Shard) از کلاستر ردیسی قرار دارد. برای تعیین شارد، از رابطه زیر استفاده میشود:
FNV−1a(key)mod3+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
.
مثالها🔗
درخواستهای موفق برای دریافت مقدار کلید🔗

terminal

output

terminal

output
درخواست ناموفق بدون کلید🔗

terminal

output
درخواست برای کلید ناموجود🔗

terminal

output
درخواست ناموفق به دلیل عدم دسترسی به ردیس🔗

terminal

output
توضیحات |
کد وضعیت |
خروجی نمونه |
دریافت مقدار موجود |
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" } |
پیادهسازی Rate Limiter، برای محافظت مزرعه!
برای محافظت از منابع سیستم در برابر حملات و بار بیش از حد، سلیب باید یک محدودکننده نرخ درخواست (Rate Limiter) بر پایهی IP کلاینت پیادهسازی کند.
مراحل پیادهسازی🔗
در هر بازه ۶۰ ثانیهای، حداکثر ۶۰ درخواست از یک IP مجاز است.
اگر تعداد خطاها از این حد بیشتر شود، کاربر باید با خطای 429 Too Many Requests
مواجه شده و از ادامه فعالیت او برای مدت زمان ۱۵ ثانیهای، جلوگیری به عمل آید.
مثالها🔗
درخواست بیش از حد مجاز🔗

terminal

output
توضیحات |
کد وضعیت |
خروجی نمونه |
درخواست در محدودۀ مجاز |
200 |
{ "key": "test", "value": "val", "status": "OK", "shard": "shard1" } |
عبور از حد مجاز درخواست |
429 |
{ "error": "Rate limit exceeded" } |
پیادهسازی Fallback، برای پشتیبانی مزرعه!
برای اطمینان از پایداری بالا و جلوگیری از قطع خدمات در زمان از کار افتادن برخی نودهای ردیسی، سلیب باید از مکانیزم (Fallback) استفاده کند.
- در زمان پردازش درخواست، اگر نود اصلی سالم باشد، به آن متصل میشویم. در غیر اینصورت، به نود Replica متصل میشویم.
- اگر هیچکدام در دسترس نبودند، پاسخ با خطای
500
و پیام Redis unavailable
بازگردانده میشود.
مراحل پیادهسازی🔗
- بررسی سلامت و دسترس پذیری نود اصلی
- اتصال به نود اصلی در صورت سالم بودن
- اتصال به نود Replica در صورت ناسالم بودن نود اصلی.
- در صورت در دسترس نبودن هر دو، بازگرداندن خطای
500
.
مثالها🔗
عدم دسترسی به نود اصلی و Replica🔗

terminal

output
توضیحات |
کد وضعیت |
خروجی نمونه |
نود اصلی سالم و قابل اتصال |
200 |
{ "key": "key5", "found": true, "value": "value5", "shard": "shard3" } |
اتصال مستقیم به replica |
200 |
{ "key": "key6", "found": true, "value": "value6", "shard": "shard2" } |
عدم دسترسی به هر دو نود |
500 |
{ "error": "Redis unavailable" } |
آنچه باید آپلود کنید🔗
توجه: پس از اعمال تغییرات، کل پروژه را Zip کرده و آپلود کنید. همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.
توجه: تنها فایلهایی که در ساختار پروژه مشخص شدهاند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایلها بیتاثیر خواهند بود.