پیاده سازی Key-Value Store به صورت همزمان در گولنگ - پیاده سازی با Fiber

Store Key-Value ها یکی از ساده‌ترین و در عین حال مؤثرترین روش‌های ذخیره‌سازی داده در برنامه‌های مدرن هستند.

در این آموزش، گام‌به‌گام ساخت NanoKV، یک Store Key-Value مینیمال و درون‌حافظه‌ای نوشته شده با زبان گولنگ و با استفاده از فریم‌ورک Fiber را بررسی خواهیم کرد.

در پایان این آموزش، شما با موارد زیر آشنا خواهید شد:

  • نحوه عملکرد Store Key-Value در پشت صحنه

  • پیاده‌سازی عملیات CRUD پایه در گولنگ

  • استفاده از هم‌زمانی (Concurrency) و TTL (زمان ماندگاری داده) برای ذخیره‌سازی موقت

  • اجرای برنامه در یک کانتینر Docker


پیش نیاز ها

قبل از شروع، مطمئن شوید موارد زیر را دارید:

  • نصب بودن زبان گو (Go)

  • آشنایی پایه با برنامه‌نویسی گولنگ

  • نصب بودن Docker (اختیاری، برای کانتینرسازی)


1. راه اندازی پروژه

اولین قدم نیازمند ساخت پروژه و راه اندازی go module هست:

mkdir NanoKV && cd NanoKV
go mod init NanoKV


2. نصب Fiber

برای ساخت API باید Fiber نصب کنیم، برای این کار با دستور زیر Fiber در پروژه نصب کنید:

go get github.com/gofiber/fiber/v2


3. ساختار پروژه

پروژه ما باید همچین ساختاری داشته باشه:

NanoKV/
│── kvstore/
│ ── kvstore.go
│── main.go
│── go.mod

│── go.sum
│── Dockerfile


4. تعریف ساختار داده

یک فایل گولنگ در مسیر kvstore/kvstore.go تعریف کنید و ساختار زیر پیاده سازی کنید:

package kvstore

import (
    "sync"
    "time"
)

type Data struct {
    value      string
    expiration time.Time
}

type KeyValueStore struct {
    mu   sync.RWMutex
    data map[string]Data
}

func NewKeyValueStore() *KeyValueStore {
    return &KeyValueStore{
       data: make(map[string]Data),
    }
}

ما از یک map برای ذخیره‌سازی جفت‌های کلید-مقدار واقعی استفاده می‌کنیم.
در زبان Go، مپ (map) یک ساختار داده داخلی است که به ما امکان می‌دهد کلیدها را به‌طور کارآمد با مقادیر مرتبط کنیم.

با این حال، از آنجا که Key-Value Store ما قرار است به‌طور هم‌زمان (concurrent) مورد دسترسی قرار گیرد (چند goroutine ممکن است هم‌زمان اقدام به خواندن یا نوشتن کنند)، نیاز به یک مکانیزم داریم تا دسترسی هم‌زمان ایمن را تضمین کنیم.

اینجاست که sync.RWMutex وارد عمل می‌شود. این یک قفل همزمانی خواندن/نوشتن (Read-Write Mutex) است که به چندین goroutine اجازه می‌دهد به‌طور همزمان داده‌ها را بخوانند، اما تضمین می‌کند که در هر لحظه فقط یک goroutine می‌تواند داده‌ها را تغییر دهد. این کار از بروز race condition جلوگیری کرده و یکپارچگی داده‌ها را حفظ می‌کند.

sync.RWMutex دو عملیات اصلی دارد:

  • RLock() و RUnlock() برای عملیات خواندن استفاده می‌شوند و به چندین خواننده اجازه می‌دهند همزمان به داده‌ها دسترسی داشته باشند.

  • Lock() و Unlock() برای عملیات نوشتن استفاده می‌شوند و تضمین می‌کنند که تنها یک goroutine در هر زمان می‌تواند داده‌ها را تغییر دهد.


5. پیاده سازی CRUD با پشتیبانی از TTL

مرحله بعدی پیاده سازی متد های set، get و delete هست:

func (kv *KeyValueStore) Set(key, val string, ttl time.Duration) {
    kv.mu.Lock()
    defer kv.mu.Unlock()

    kv.data[key] = Data{
       value:      val,
       expiration: time.Now().Add(ttl),
    }
}

func (kv *KeyValueStore) Get(key string) (string, bool) {
    kv.mu.Lock()
    defer kv.mu.Unlock()
    val, ok := kv.data[key]

    // Check for Item Expiry
    if val.expiration.IsZero() || time.Now().Before(val.expiration) {
       return val.value, ok
    }

    // Delete if Expired
    delete(kv.data, key)
    return "", false
}

func (kv *KeyValueStore) Delete(key string) bool {
    kv.mu.Lock()
    defer kv.mu.Unlock()

    _, ok := kv.data[key]
    if !ok {
       return false
    }
    delete(kv.data, key)
    return true
}

متد Set: یک Key-Value را همراه با زمان انقضا ذخیره می‌کند. اگر TTL (زمان ماندگاری) تنظیم شده باشد، زمان انقضای دقیق محاسبه می‌شود. این تابع قبل از نوشتن قفل (Lock) را فعال می‌کند و پس از اتمام، آن را باز می‌کند.

متد Get: یک مقدار را برمیگردونه. اگر کلید وجود داشته باشد و منقضی نشده باشد، مقدار را برمی‌گرداند. در صورت منقضی شدن، کلید را حذف کرده و false بازمی‌گرداند.

متد Delete: یک Key-Value را حذف می‌کند. اگر کلید وجود داشته باشد، آن را حذف کرده و true بازمی‌گرداند؛ در غیر این صورت، false بازمی‌گردد.


6. پیاده سازی HTTP Server

یک فایل به نام main.go ایجاد کنید و کد زیر پیاده سازی کنید:

package main

import (
    "time"
    "NanoKV/kvstore"
    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()
    kv := kvstore.NewKeyValueStore()

    app.Get("/", func(c *fiber.Ctx) error {
       return c.SendString("This is a Simple Key-Value store like Redis in Go.")
    })

    app.Get("/get/:key", func(c *fiber.Ctx) error {
       key := c.Params("key")
       value, ok := kv.Get(key)
       if !ok {
          return c.SendString("The Key " + key + " doesn't exist")
       }
       return c.SendString("The Key " + key + " has Value " + value)
    })

    app.Post("/set/:key/:value", func(c *fiber.Ctx) error {
       key := c.Params("key")
       value := c.Params("value")
       kv.Set(key, value, 10*time.Minute)
       return c.SendString("Key " + key + " Value " + value)
    })

    app.Delete("/delete/:key", func(c *fiber.Ctx) error {
       key := c.Params("key")
       ok := kv.Delete(key)
       if !ok {
          return c.SendString("The Key " + key + " doesn't exist")
       }
       return c.SendString("Successfully Deleted!!")
    })

    app.Listen(":3000")
}

GET /get/:key: مقدار کلید مشخص‌شده را برمیگردونه اگر وجود داشته باشد؛ در غیر این صورت، متن کلید وجود ندارد برمی‌گرداند.

POST /set/:key/:value: یک کلید-مقدار را با TTL پیش‌فرض ۱۰ دقیقه ذخیره می‌کند.

DELETE /delete/:key: یک کلید را حذف می‌کند اگر وجود داشته باشد.


7. اجرا سرور

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

go run main.go

با CURL میتونید تست کنید:

curl -X POST http://localhost:3000/set/foo/bar
curl -X GET http://localhost:3000/get/foo
curl -X DELETE http://localhost:3000/delete/foo


8. داکرایز کردن پروژه

برای داکرایز کردن پروژه، نیاز دارید یک فایل به نام Dockerfile ایجاد کنید و کد زیر درونش قرار بدید:

FROM golang:alpine

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY . ./

RUN go build -o NanoKV .

EXPOSE 3000

CMD ["/app/NanoKV"]


بیلد و اجرا کردن کانتینر داکر

برای بیلد و اجرا کانتینر دستور زیر اجرا کنید:

docker build -t nanokv .
docker run -p 3000:3000 nanokv


منبع

1 🔥
1 🎉
1 😮
1 👍
2 💜
1 👏
میلاد خسروی
نویسنده کد نیوز

برنامه نویس فان | Fun Developer یک آدم ساده که عاشق برنامه نویسی و کد زدنه :) تلاش میکنه تا به بقیه کمک کنه. توسعه دهنده هسته لاراول و فضای اوپن سورس. فاندر پرانتز و کد نیوز.

0+ نظر

برای ثبت نظر ابتدا ورود کنید.

0 نظر

    اولین نفر باش که نظر ثبت میکنی :) یعنی یه کامنت به ما نمیرسه 😁