понедельник, 22 марта 2021 г.

Работа с Redis, пакет github.com/gomodule/redigo/redis

Пакет redis (github.com/gomodule/redigo/redis) - это клиент для базы данных Redis.

Подключения

Интерфейс Conn - это основной интерфейс для работы с Redis. Приложения создают соединения, вызывая функции Dial, DialWithTimeout или NewConn. В будущем будут добавлены функции для создания сегментированных и других типов подключений.

Приложение должно вызвать метод Close соединения, когда приложение завершит соединение.

Выполнение команд

Интерфейс Conn имеет общий метод для выполнения команд Redis:

Do(commandName string, args ...interface{}) (reply interface{}, err error)

В справочнике по командам Redis (http://redis.io/commands) перечислены доступные команды. Пример использования команды Redis APPEND:

n, err := conn.Do("APPEND", "key", "value")

Метод Do преобразует аргументы команды в bulk строки для передачи на сервер следующим образом:

Go тип                Преобразование
[]byte                Отправляется как есть
string                Отправляется как есть
int, int64            strconv.FormatInt(v)
float64               strconv.FormatFloat(v, 'g', -1, 64)
bool                  true -> "1", false -> "0"
nil                   ""
все остальные типы    fmt.Fprint(w, v)

Типы ответов команды Redis представлены с использованием следующих типов Go:

Redis тип               Go тип
error                   redis.Error
integer                 int64
simple string           string
bulk string             []byte или nil если значение отсутствует.
array                   []interface{} или nil если значение отсутствует.

Используйте утверждения типа или вспомогательные функции ответа для преобразования из interface{} в конкретный тип Go для результата команды.

Конвейерная обработка

Соединения поддерживают конвейерную обработку с использованием методов Send, Flush и Receive.

Send(commandName string, args ...interface{}) error
Flush() error
Receive() (reply interface{}, err error)

Send записывает команду в выходной буфер соединения. Flush сбрасывает выходной буфер соединения на сервер. Receive читает один ответ от сервера. В следующем примере показан простой конвейер.

c.Send("SET", "foo", "bar")
c.Send("GET", "foo")
c.Flush()
c.Receive() // ответ от SET
v, err = c.Receive() // ответ от GET

Метод Do сочетает в себе функциональность методов Send, Flush и Receive. Метод Do начинается с записи команды и очистки выходного буфера. Затем метод Do получает все ожидающие ответы, включая ответ на команду, только что отправленную Do. Если какой-либо из полученных ответов является ошибкой, то Do возвращает ошибку. Если ошибок нет, Do возвращает последний ответ. Если аргумент команды для метода Do - "", то метод Do очистит выходной буфер и получит ожидающие ответы без отправки команды.

Используйте методы Send и Do для реализации конвейерных транзакций.

c.Send("MULTI")
c.Send("INCR", "foo")
c.Send("INCR", "bar")
r, err := c.Do("EXEC")
fmt.Println(r) // печатает [1, 1]

Конкурентность

Соединения поддерживают одного конкурентного вызывающего объекта метода Receive и одного конкурентного вызывающего объекта методов Send и Flush. Никакой другой конкурентности не поддерживается, включая конкурентные вызовы методов Do и Close.

Для полного одновременного доступа к Redis используйте потокобезопасный пул для получения, использования и освобождения соединения из горутины. Подключения, возвращаемые из пула, имеют ограничения конкурентности, описанные в предыдущем абзаце.

Опубликовать и подписаться

Используйте методы Send, Flush и Receive для реализации подписчиков Pub/Sub.

c.Send("SUBSCRIBE", "example")
c.Flush()
for {
    reply, err := c.Receive()
    if err != nil {
        return err
    }
    // обрабатываем отправленное сообщение
}

Тип PubSubConn оборачивает Conn удобными методами для реализации подписчиков. Методы Subscribe, PSubscribe, Unsubscribe и PUnsubscribe отправляют и сбрасывают команду управления подпиской. Метод получения преобразует отправленное сообщение в удобные типы для использования в переключателе типа.

psc := redis.PubSubConn{Conn: c}
psc.Subscribe("example")
for {
    switch v := psc.Receive().(type) {
    case redis.Message:
        fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
    case redis.Subscription:
        fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
    case error:
        return v
    }
}

Помощники ответа

Функции Bool, Int, Bytes, String, Strings и Values ​​преобразуют ответ в значение определенного типа. Чтобы обеспечить удобную упаковку вызовов методов соединения Do и Receive, функции принимают второй аргумент типа error. Если ошибка не равна nil, то вспомогательная функция возвращает ошибку. Если ошибка равна nil, функция преобразует ответ в указанный тип:

exists, err := redis.Bool(c.Do("EXISTS", "foo"))
if err != nil {
    // обрабатываем возврат ошибки из c.Do 
    // или ошибку преобразования типа.
}

Функция Scan преобразует элементы ответа массива в типы Go:

var value1 int
var value2 string
reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
if err != nil {
    // обрабатываем ошибки
}
 if _, err := redis.Scan(reply, &value1, &value2); err != nil {
    // обрабатываем ошибки
}

Ошибки

Методы подключения возвращают с сервера ответы об ошибках в виде типа redis.Error.

Вызовите метод соединения Err(), чтобы определить, возникла ли в соединении неисправимая ошибка, например сетевая ошибка или ошибка анализа протокола. Если Err() возвращает значение, отличное от nil, соединение непригодно для использования и должно быть закрыто.

Пример (Zpop)

В этом примере реализуется ZPOP, как описано на http://redis.io/topics/transactions, с использованием WATCH/MULTI/EXEC и сценариев.

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

// zpop извлекает значение из ключа ZSET 
// с помощью команд WATCH/MULTI/EXEC.
func zpop(c redis.Conn, key string) (result string, err error) {

	defer func() {
		// Вернуть соединение в нормальное состояние 
		// при ошибке.
		if err != nil {
			c.Do("DISCARD") // nolint: errcheck
		}
	}()

	// Цикл, пока транзакция не будет успешной.
	for {
		if _, err := c.Do("WATCH", key); err != nil {
			return "", err
		}

		members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
		if err != nil {
			return "", err
		}
		if len(members) != 1 {
			return "", redis.ErrNil
		}

		if err = c.Send("MULTI"); err != nil {
			return "", err
		}
		if err = c.Send("ZREM", key, members[0]); err != nil {
			return "", err
		}
		queued, err := c.Do("EXEC")
		if err != nil {
			return "", err
		}

		if queued != nil {
			result = members[0]
			break
		}
	}

	return result, nil
}

// zpopScript извлекает значение из ZSET.
var zpopScript = redis.NewScript(1, `
    local r = redis.call('ZRANGE', KEYS[1], 0, 0)
    if r ~= nil then
        r = r[1]
        redis.call('ZREM', KEYS[1], r)
    end
    return r
`)

// В этом примере реализуется ZPOP, как описано в
// http://redis.io/topics/transactions 
// с использованием WATCH/MULTI/EXEC и сценариев.
func main() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	// Добавляем тестовые данные с помощью конвейера.

	for i, member := range []string{"red", "blue", "green"} {
		if err = c.Send("ZADD", "zset", i, member); err != nil {
			fmt.Println(err)
			return
		}
	}

	if _, err := c.Do(""); err != nil {
		fmt.Println(err)
		return
	}

	// Извлечь с помощью WATCH/MULTI/EXEC

	v, err := zpop(c, "zset")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(v)

	// Извлечь с помощью скрипта.

	v, err = redis.String(zpopScript.Do(c, "zset"))
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(v)

}

Вывод:

red
blue


Читайте также:


Комментариев нет:

Отправить комментарий