Введение в распределённое кэширование

В мире разработки программного обеспечения производительность играет ключевую роль. Один из наиболее эффективных способов повысить производительность вашего приложения — это реализовать систему распределённого кэширования. Представьте себе сценарий, где ваше приложение может получать данные за миллисекунды вместо секунд благодаря продуманно спроектированному кэшу, который распространяется на несколько узлов. Здесь на помощь приходит Hazelcast, а в паре с Go он становится мощным инструментом для создания масштабируемых и высокопроизводительных приложений.

Что такое Hazelcast?

Hazelcast — это сетка данных в памяти, которая позволяет хранить и управлять данными распределённым образом. Она поддерживает различные структуры данных, такие как карты, множества, списки и очереди, к которым можно обращаться и манипулировать ими в распределённой среде. Hazelcast особенно полезен для приложений, требующих низкой задержки доступа к данным и высокой пропускной способности.

Настройка клиента Hazelcast Go

Чтобы начать использовать Hazelcast в своём приложении на Go, вам необходимо настроить клиент Hazelcast Go. Вот как вы можете это сделать:

Установка клиента Hazelcast Go

Сначала вам нужно установить пакет клиента Hazelcast Go. Вы можете сделать это с помощью следующей команды:

go get github.com/hazelcast/hazelcast-go-client

Базовая конфигурация и подключение

Вот простой пример того, как подключиться к кластеру Hazelcast с помощью клиента Go:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    // Запуск клиента с настройками по умолчанию.
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        panic(err)
    }
    defer client.Shutdown(ctx)

    // Получение ссылки на карту.
    myMap, err := client.GetMap(ctx, "my-map")
    if err != nil {
        panic(err)
    }

    // Сохранение значения в карте.
    err = myMap.Set(ctx, "some-key", "some-value")
    if err != nil {
        panic(err)
    }

    // Получение значения из карты.
    value, err := myMap.Get(ctx, "some-key")
    if err != nil {
        panic(err)
    }

    fmt.Printf("Значение для ключа 'some-key': %s\n", value)
}

Пользовательская конфигурация

Если вам нужен больший контроль над конфигурацией клиента, вы можете создать пользовательскую hazelcast.Config и передать её в hazelcast.StartNewClientWithConfig:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
    "github.com/hazelcast/hazelcast-go-client/types"
    "time"
)

func main() {
    ctx := context.TODO()
    config := hazelcast.Config{}
    config.Cluster.InvocationTimeout = types.Duration(3 * time.Minute)
    config.Cluster.Network.ConnectionTimeout = types.Duration(10 * time.Second)

    client, err := hazelcast.StartNewClientWithConfig(ctx, config)
    if err != nil {
        panic(err)
    }
    defer client.Shutdown(ctx)

    // Остальной ваш код здесь...
}

Использование структур данных Hazelcast

Hazelcast предоставляет множество распределённых структур данных, которые вы можете использовать в своём приложении Go.

Карты

Карты являются одной из наиболее часто используемых структур данных в Hazelcast. Вот как вы можете их использовать:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        panic(err)
    }
    defer client.Shutdown(ctx)

    myMap, err := client.GetMap(ctx, "my-map")
    if err != nil {
        panic(err)
    }

    // Сохраняем значение в карте.
    err = myMap.Set(ctx, "some-key", "some-value")
    if err != nil {
        panic(err)
    }

    // Получаем значение из карты.
    value, err := myMap.Get(ctx, "some-key")
    if err != nil {
        panic(err)
    }

    fmt.Printf("Значение для ключа 'some-key': %s\n", value)
}

Множества

Множества полезны для хранения уникальных элементов. Вот пример использования множества:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        panic(err)
    }
    defer client.Shutdown(ctx)

    mySet, err := client.GetSet(ctx, "my-set")
    if err != nil {
        panic(err)
    }

    // Добавляем элементы во множество.
    changed, err := mySet.AddAll(ctx, "value1", "value2", "value1", "value3")
    if err != nil {
        panic(err)
    }

    fmt.Printf("Добавлено %v элементов во множество\n", changed)
}

Списки и очереди

Списки и очереди также поддерживаются в Hazelcast. Вот как вы можете их использовать:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        panic(err)
    }
    defer client.Shutdown(ctx)

    myList, err := client.GetList(ctx, "my-list")
    if err != nil {
        panic(err)
    }

    // Добавляем элементы в список.
    _, err = myList.AddAll(ctx, "tic", "toc", "tic")
    if err != nil {
        panic(err)
    }

    myQueue, err := client.GetQueue(ctx, "my-queue")
    if err != nil {
        panic(err)
    }

    // Добавляем элемент в очередь.
    added, err := myQueue.Add(ctx, "item 1")
    if err != nil {
        panic(err)
    }

    fmt.Printf("Добавлен %v элемент в очередь\n", added)
}

Реализация кэша с записью через MapStore

Одной из мощных функций Hazelcast является возможность реализации кэша с записью через интерфейс MapStore. Это позволяет вам обновлять как кэш, так и базовый хранилище данных одновременно.

Вот пример того, как вы можете реализовать MapStore для подключения к базе данных MongoDB:

package main

import (
    "context"
    "fmt"
    "github.com/hazelcast/hazelcast-go-client"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type MongoPersonMapStore struct {
    mongoClient *mongo.Client
    collection  *mongo.Collection
}

func (m *MongoPersonMapStore) Init(hazelcastInstance interface{}, properties map[string]interface{}, mapName string) error {
    mongoURI := properties["uri"].(string)
    mongoClient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoURI))
    if err != nil {
        return err
    }
    m.mongoClient = mongoClient
    m.collection = mongoClient.Database("mydatabase").Collection("mypersons")
    return nil
}

func (m *MongoPersonMapStore) Store(ctx context.Context, key interface{}, value interface{}) error {
    person := value.(*Person)
    _, err := m.collection.InsertOne(ctx, person)
    return err
}

func (m *MongoPersonMapStore) Load(ctx context.Context, key interface{}) (interface{}, error) {
    var person Person
    err := m.collection.FindOne(ctx, key).Decode(&person)
    return &person, err
}

func (m *MongoPersonMapStore) Delete(ctx context.Context, key interface{}) error {
    _, err := m.collection.DeleteOne(ctx, key