Кэширование в Redis с Двойным Ключом. Окончание
Начало
Реализация Двойного Ключа в .NET
1. DTO + Хелпер для ключей Redis
public class UserDto
{
public Guid UserId { get; set; }
public string Email { get; set; }
public string DisplayName { get; set; }
public string TimeZone { get; set; }
}
public static class UserKeys
{
public static string Data(Guid id)
=> $"user:data:{id}";
public static string Email(string email)
=> $"user:email:{email.ToLowerInvariant()}";
}
2. Кэширование пользователя (атомарная запись)
public async Task CacheUserAsync(UserDto user)
{
var dataKey = UserKeys.Data(user.UserId);
var emailKey = UserKeys.Email(user.Email);
var json = JsonSerializer.Serialize(user);
var tran = _db.CreateTransaction();
tran.StringSetAsync(dataKey, json, TimeSpan.FromMinutes(10));
tran.StringSetAsync(emailKey, user.UserId.ToString(), TimeSpan.FromMinutes(10));
await tran.ExecuteAsync();
}
- оба ключа обновляются совместно;
- исключён риск частичных записей;
- JSON хранится 1 раз.
3. Поиск по UserId
public async Task<UserDto?>
GetByIdAsync(Guid userId)
{
var json = await _db.StringGetAsync(
UserKeys.Data(userId));
return json.IsNullOrEmpty
? null
: JsonSerializer.Deserialize<UserDto>(json);
}
4. Поиск по Email
public async Task<UserDto?>
GetByEmailAsync(string email)
{
var id = await _db.StringGetAsync(UserKeys.Email(email));
if (string.IsNullOrEmpty(id)) return null;
var userId = Guid.Parse(id);
return await GetByIdAsync(userId);
}
5. Безопасная обработка изменений email
public async Task UpdateEmailAsync(
Guid userId,
string oldEmail,
string newEmail)
{
var oldKey = UserKeys.Email(oldEmail);
var newKey = UserKeys.Email(newEmail);
var tran = _db.CreateTransaction();
// ... обновляем данные пользователя
tran.KeyDeleteAsync(oldKey);
tran.StringSetAsync(newKey, userId.ToString());
await tran.ExecuteAsync();
}
- старый индекс удаляется, добавляется новый;
- ключ данных не изменяется;
- нет дублирования JSON;
- нет несогласованного состояния кэша.
Если вы не будете использовать кэширование с двойным ключом, то рано или поздно, вы столкнетесь с…
- устаревшими данными в кэше;
- неработающим входом в систему (изменён email, но кэш не обновился);
- внутренними микросервисами, возвращающими устаревшие значения;
- «фантомными пользователями» в логах;
- и т.п.
Большинство этих ошибок никогда не проявятся в процессе разработки — только в рабочей среде под реальной нагрузкой.
Шаблон кэширования с двойным ключом универсален для современных систем:
1. Электронная коммерция
- ProductId → данные
- Артикул → ProductId
2. CMS
- ContentId → данные
- Slug → ContentId
3. IoT
- DeviceId → данные
- MAC-адрес/Серийный номер → DeviceId
Итого
Кэширование Redis с двойным ключом — не оптимизация, а фундаментальная архитектура для современных систем .NET, использующих Redis. Его следует использовать, когда:
- сущность имеет несколько идентификаторов;
- один или несколько из этих идентификаторов изменяемы;
- необходим быстрый поиск из разных контекстов;
- нужно избежать дублирования JSON в Redis;
- важна согласованность кэша под нагрузкой.
Правильный шаблон:
- один канонический ключ данных;
- несколько легковесных ключей индекса;
- атомарные обновления для обеспечения согласованности.
Так вы избежите почти всех проблем с несогласованностью кэша еще до их появления.
Источник: https://thecodeman.net/posts/dual-key-redis-caching-in-dotnet