Совместима с платформами .NET Core 3.1+
.
MyLab.Redis
предоставляет асинхронную объектную модель для работы с ключами Redis
, ряд решений на базе этой объектной модели, конфигурирование и интеграцию с использованием механизмов .NET Core
. Базируется на StackExchange.Redis.
При работе с инструментами Redis
применяется подход с уклоном в DSL (Domain Specific Language):
await redis.Db().String("foo").SetAsync("bar");
Интеграция в приложение:
public class Startup
{
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//...
serviceCollection
.AddRedis(RedisConnectionStrategy.Lazy)
.ConfigureRedis(Configuration);
//...
}
}
Применение:
class PingService
{
private readonly IRedisService _redis;
public PingService(IRedisService redis)
{
_redis = redis;
}
public Task<TimeSpan> PingAsync()
{
return _redis.Server().PingAsync();
}
}
Интеграция осуществляется стандартным способом - добавлением сервисов библиотеки в коллекцию сервисов в методе ConfigureService
стартового класса приложения.
public class Startup
{
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//...
serviceCollection.AddRedis(RedisConnectionStrategy.Lazy)
//...
}
}
В целевые классы инструменты библиотеки интегрируются через сервис с интерфейсом MyLab.Redis.IRedisService
.
В методе AddRedis
требуется указание стратегии подключения к Redis
типа RedisConnectionStrategy
:
Lazy
- подключение устанавливается при попытке отправить запрос вRedis
, если подключение не установлено. Особенности:- происодит блокировка потоков в момент проверки состояния подключения и на время процесса подключения;
- попытка подключения происходит по требованию: если
Redis
не используется, то и подключение не будет установлено;
Background
- подключение устанавливается сразу после запуска приложения. Особенности:- при неудачах, повторные попытки первичного подключения будут повторяться бесконечно;
- при разрыве, будут осуществляться бесконечные попытки восстановить подключение;
- при попытке сделать запрос в
Redis
, если подключение не устновлено, будет выдано исключениеRedisNotConnectedException
.
По умолчанию конфигурация загружается из секции Redis
. Ниже приведён пример указания кастомной секции:
serviceCollection.ConfigureRedis(Configuration, "CustomSection");
Кроме того, опции настройки сервиса можно определять в коде:
serviceCollection.ConfigureRedis(opt => opt.Password = "***");
Класс настроек:
/// <summary>
/// Contains Redis configuration
/// </summary>
public class RedisOptions
{
/// <summary>
/// Connection string
/// </summary>
/// <remarks>https://stackexchange.github.io/StackExchange.Redis/Configuration</remarks>
public string ConnectionString { get; set; }
/// <summary>
/// Overrides password from <see cref="ConnectionString"/>
/// </summary>
public string Password { get; set; }
/// <summary>
/// Retry period in seconds when 'background' connection mode
/// </summary>
public int BackgroundRetryPeriodSec { get; set; } = 10;
//...
}
Смотрите также:
Для выполнения команд уровня сервера, такие как ECHO
, PING
, KEYS
, SCAN
, получите доступ через метод Server
сервиса Redis:
await redis.Server().PingAsync();
Метод Server
предоставляет доступ к серверу по умолчанию - первому в списке серверов в строке подключения. Для выбора конкретного сервера, необходимо воспользоваться перегрузкой этого метода: Server(EndPoint endPoint)
.
Доступ к инструментам уровня баз данных получите через метод Db
сервиса Redis:
await redis.Db().String("foo").SetAsync("bar");
Метод Db
предоставляет доступ к базе данных по умолчанию - с индексом 0 или соответствующим значением из строки подключения. Для выбора конкретной бfps данных, необходимо воспользоваться перегрузкой этого метода: Db(int dbIndex)
.
Команды уровня базы данных для работы с ключами сгруппированы по объектным моделям основных типов ключей. Доступ к этим ключам можно получить через соответствующие методы:
-
String -
_redis.Db().String(string key)
-
Hash -
_redis.Db().Hash(string key)
-
Set -
_redis.Db().Set(string key)
-
SortedSet -
_redis.Db().SortedSet(string key)
-
List -
_redis.Db().List(string key)
Пример:
await redis.Db().String("foo").ExpireAsync(TimeSpan.FromMilliseconds(100));
Этот функционал временно недоступен.
RedisCahce
- реализация распределённого кэша на базе Redis
.
Для того, чтобы начать работать с кэшем необходимо:
- сконфигурировать кэш
- получить кэш в коде по имени
Конфигурирование осуществляется через общий узел конфигурации библиотеки - поле Caching
:
/// <summary>
/// Contains Redis configuration
/// </summary>
public class RedisOptions
{
//...
/// <summary>
/// Caching options
/// </summary>
public CachingOptions Caching { get; set; }
//...
}
/// <summary>
/// Contains caching options
/// </summary>
public class CachingOptions
{
/// <summary>
/// Gets Redis-key name prefix
/// </summary>
public string KeyPrefix { get; set; }
/// <summary>
/// Get named cache options
/// </summary>
public CacheOptions[] Caches { get; set; }
}
/// <summary>
/// Cache options
/// </summary>
public class CacheOptions
{
/// <summary>
/// Cache name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Default expiry for cache items
/// </summary>
public string DefaultExpiry { get; set; }
}
Смотрите также: Конфигурация
Имя ключа для хранения кэша формируется из префикса имени ключа Redis.Caching.KeyPrefix
и имени конкретного кэша Redis.Caches[].Name
по шаблону prefix:name
. Или используется только имя, если префикс указан, как пустой. По умолчанию, префикс имеет значение cache
.
Время экспирации указываетя только для конкретного кэша в поле Redis.Caches[].DefaultExpiry
. Это значение используется по умолчанию для элементов кэша, если в коде не указано другое значение при добавлении этого элемента.
Значение по умолчанию - 1 мин.
Возможные форматы:
int
- число секунд;TimeStemp
- временной интервал. Например00:00:10
- 10 секунд. Подробнее тут.
{
"ConnectionString" : "localhost:9110,allowAdmin=true",
"Cache": [
{
"Name": "test",
"DefaultExpiry": "24"
}
]
}
Добавляет или заменяет значение в кэше, обновляет TTL
на значение по умолчанию:
await redis.Db().Cache("test").AddAsync("foo", obj);
Можно указать кастомное значение TTL
:
await redis.Db().Cache("test").AddAsync("foo", obj, TimeSpan.FromSeconds(5));
Удаляет элемент из кэша:
await redis.Db().Cache("test").Remove("foo")
Изменяет время жизни элемента кэша:
await redis.Db().Cache("test").UpdateExpiryAsync("foo", TimeSpan.FromSeconds(5));
Получает значение из кэша или значение по умолчанию:
var found = await redis.Db().Cache("test").TryFetchAsync<T>("foo");
if(found != null)
{
// found
}
else
{
// not found
}
Получает значение из кэша или создаёт новый объект, кэширует его и возвращает, как результат:
var found = await cache.FetchAsync("foo",
() => new CacheItem
{
Id = 1
});
Реализация алгоритма распределённой блокировки с пирменением Redis.
Позволяет синхронизировать процессы. При этом состояние блокировки находится в Redis
.
В конфигурации приложения объявляются именованные блокировки и объявляются их временные параметры. В коде приложение может воспользоваться блокировкой с указанием её имени. При этом будут использованы соответствующие параметы из конфигурации.
При необходимости синхронизации, приложение может попытаться заблокировать/захватить состояние и таким образом получить возможность выполнить действия, которые требуют синхронизации. Такая попытка может закончиться неудачно, если за отведённое время состояние не освободилось.
При необходимости длительной блокировки состояния следует периодически продлевать время блокировки. Например, каждую итерацию в задаче, которая выполняет длительный цикл.
Такая блокировка работает и при использовании в рамках одного процесса. Т.е. в общем случае, если работает несколько экземпляров приложения (процессы), в которых работает несколько потоков, то они могут конкурировать в независимости от того, в каком процессе они находятся.
Конфигурирование осуществляется через общий узел конфигурации библиотеки - поле Locking
:
/// <summary>
/// Contains Redis configuration
/// </summary>
public class RedisOptions
{
....
/// <summary>
/// Locking options
/// </summary>
public LockingOptions Locking { get; set; }
....
}
/// <summary>
/// Contains licking options
/// </summary>
public class LockingOptions
{
/// <summary>
/// Gets Redis-key name prefix
/// </summary>
public string KeyPrefix { get; set; }
/// <summary>
/// Gets named lock options
/// </summary>
public LockOptions[] Locks { get; set; }
}
/// <summary>
/// Lock options
/// </summary>
public class LockOptions
{
/// <summary>
/// Lock name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Determines key expiry
/// </summary>
public string Expiry { get; set; }
/// <summary>
/// Determines the timeout for a locking attempt
/// </summary>
public string DefaultTimeout { get; set; }
/// <summary>
/// Determines a waiting period between locking attempts
/// </summary>
public string RetryPeriod { get; set; }
}
Смотрите также: Конфигурация
Имя ключа для организации блокировки формируется из префикса имени ключа Redis.Locking.KeyPrefix
и имени конкретного кэша Redis.Locks[].Name
по шаблону prefix:name
. Или используется только имя, если префикс указан, как пустой. По умолчанию, префикс имеет значение redlock
.
Параметры конфиграции:
Redis.Locking.Locks[].Expiry
- время жизни ключа, используемого для организации блокировки. Значение по умолчанию 1 мин;Redis.Locking.Locks[].DefaultTimeout
- время, в течении которого происходят попытки блокировки. Используется по умолчанию, если не указано другое значение в коде. Значение по умолчанию - 5 сек;Redis.Locking.Locks[].RetryPeriod
- период повторов попыток блокировки.
Все эти временные параметры могут быть указаны в следующих форматах:
int
- число секунд;TimeStemp
- временной интервал. Например00:00:10
- 10 секунд. Подробнее тут.
var locker = redis.Db().CreateLocker("foo"); // 1
await using var lockAttempt = await locker.TryLockOnceAsync(); // 2
if(lockAttempt.Acquired) // 3
{
// 4
}
В примере выше:
- создаётся блокировщик по имени блокировки
foo
; - происходит разовая попытка завладеть состоянием;
- проверка успешности захвата состояния;
- выполнение работы, требующей синхронизации.
var locker = redis.Db().CreateLocker("orders", "c389c0a5e28b42258298a1ab72ff41d0");
//....
В примере выше создаётся блокировщик по имени блокировки orders
(заявки) и идентификатору конкретной заявки.
var locker = redis.Db().CreateLocker("foo"); // 1
await using var lockAttempt = await locker.TryLockOnceAsync(); // 2
if(lockAttempt.Acquired) // 3
{
while(/*...*/) // 4
{
// 5
await lockAttempt.Lock.ProlongAsync(); // 6
}
}
В примере выше:
- создаётся блокировщик по имени блокировки
foo
; - происходит разовая попытка завладеть состоянием;
- проверка успешности захвата состояния;
- выполнение предположительно длительного цикла;
- выполнение работы, требующей синхронизации;
- продление действия блокировки;
В таком случае при каждой итерации будет обновляться время экспирации блокировки. Поэтому необходимо подбирать параметр блокировки Expiry
таким образом, чтобы его гарантировано хватало на выполнение одной итерации цикла.
Инструменты для работы со скриптами доступны из объекта БД через метод Script()
:
var scripting = redis.Db().Script();
Для выполнения скрипта используется DSL-методы сборки и выполнения запроса. DSL выражение начинется с определния целевого скрипта. Целевой скрипт может быть передан в качестве аргумента (EVAL) или предварительно загружен в кэш Redis
(EVALSHA).
Пример указания скрипта по месту:
redis.Db().Script().Inline("return 10");
Пример указания предварительно загруженного к кэш скрипта:
redis.Db().Script().BySha("eb29cbbc2f5f39dfd02b7b8d3046d9ee7754d966");
Остальная часть выражения состоит из необязательных указаний используемых в скрипте ключей и аргументов. Завершает выражение метод выполнения команды.
Пример выполнения скрипта с передачей ключа и аргумента:
await redis.Db().Script()
.Inline("return redis.call('set', KEYS[1], ARGV[1])")
.WithKey(key)
.WithArgs("foo")
.EvaluateAsync();
Реализовано с использованием SCRIPT LOAD.
var sha1 = await redis.Db().Script().LoadAsync("return 10");
Реализовано с использованием SCRIPT KILL.
await redis.Db().Script().KillCurrentAsync();
Реализовано с использованием SCRIPT EXISTS.
Пример для одного аргумента:
bool exists = await redis.Db().Script().ExistsAsync("eb29cbbc2f5f39dfd02b7b8d3046d9ee7754d966");
Пример для проверки нескольких скриптов:
bool[] exists = await redis.Db().Script().ExistsAsync(
"eb29cbbc2f5f39dfd02b7b8d3046d9ee7754d966",
"ed84a94a3ba9d63db7a4255cb8940890ef4a08fc",
"70a65c9e5784d36b4f42389fa93da5bbe9578be5"
);
Реализовано с использованием SCRIPT FLUSH SYNC.
await redis.Db().Script().FlushCacheAsync();
Проверка работоспособности заключается в проверке наличия подключения к Redis
и осуществляется через механизм HealthCheck.
Для подключения проверки необходимо использовать метод AddRedis
для построителя проверок, как показано на примере ниже:
services.AddHealthChecks().AddRedis();