Я работаю над системой, в которой я кэширую постраничные результаты с помощью Redis. Логика кэширования хранит данные для текущей страницы, следующей страницы и страницы после нее. Ключ кэша каждой страницы основан на индексе страницы и хэше предиката запроса. Проблема возникает, когда предикат запроса изменяется. Мне нужно убедиться, что кэш отражает результаты нового предиката и что все старые записи кэша, связанные с предыдущим предикатом, аннулированы.
В частности, я реализовал систему кэширования, которая обрабатывает: Хранение данных для текущей страницы и следующих двух страниц. Генерацию ключей кэша на основе индекса страницы и хэша предиката запроса.
Ниже приведены мои codeы, которые помогут вам.
Чего я хочу добиться:
Когда новый запрос выполняется с другим предикатом, я хочу убедиться, что старые записи кэша аннулированы, а новые данные извлекаются из базы данных и кэшируются на основе нового предиката. Это включает в себя очистку кэша для страниц, связанных со старым предикатом, и сохранение новых страниц в соответствии с обновленным предикатом.
Что я пробовал: Я использовал хэш предиката для создания уникального ключа кэша. Однако я не уверен, как эффективно управлять аннулированием кэша при изменении предикатов, особенно как обрабатывать записи кэша для нескольких страниц, которые могут быть затронуты.
public class GetDataPagedListQueryHandler : IRequestHandler<GetDataPagedListQueryRequest, OptResult<PaginatedList<GetDataPagedListQueryResponse>>>
{
private readonly IDepartmentService _departmentService;
private readonly IMapper _mapper;
public GetDataPagedListQueryHandler(IDepartmentService departmentService, IMapper mapper)
{
_departmentService = departmentService;
_mapper = mapper;
}
public async Task<OptResult<PaginatedList<GetDataPagedListQueryResponse>>> Handle(GetDataPagedListQueryRequest request, CancellationToken cancellationToken)
{
return await ExceptionHandler.HandleOptResultAsync(async () =>
{
var model = _mapper.Map<GetAllPaged_Index_Dto>(request);
var result = await _departmentService.GetDataPagedForDepartment(model);
var response = _mapper.Map<PaginatedList<GetDataPagedListQueryResponse>>(result.Data);
if (response != null)
return await OptResult<PaginatedList<GetDataPagedListQueryResponse>>.SuccessAsync(response, Messages.Successfull);
return await OptResult<PaginatedList<GetDataPagedListQueryResponse>>.FailureAsync(Messages.UnSuccessfull);
});
}
}
}
[Service(ServiceLifetime.Scoped)]
public class DepartmentService : IDepartmentService
{
private readonly IDepartmentReadRepository _readRepository;
private readonly IDepartmentWriteRepository _writeRepository;
private readonly DepartmentSpecifications _departmentSpecifications;
private readonly IRedisCacheService _redisCacheService;
public DepartmentService(IDepartmentReadRepository readRepository, IDepartmentWriteRepository writeRepository, DepartmentSpecifications departmentSpecifications, IRedisCacheService redisCacheService)
{
_readRepository = readRepository;
_writeRepository = writeRepository;
_departmentSpecifications = departmentSpecifications;
_redisCacheService = redisCacheService;
}
public async Task<OptResult<PaginatedList<Department>>> GetDataPagedForDepartment(GetAllPaged_Index_Dto model)
{
var predicate = _departmentSpecifications.GetDataPagedListPredicate(model);
if (string.IsNullOrEmpty(model.OrderBy)) model.OrderBy = "DepartmentName ASC";
PaginatedList<Department> pagedDepartments;
if (!string.IsNullOrEmpty(model.SearchText) || !_redisCacheService.IsConnected)
pagedDepartments = await _readRepository.GetDataPagedAsync(predicate, "", model.PageIndex, model.Take, model.OrderBy);
else
pagedDepartments = await _redisCacheService.GetPaginatedListAsync("departments", model.PageIndex, async pageIndex =>
await _readRepository.GetDataPagedAsync(predicate, "", pageIndex, model.Take, model.OrderBy));
return await OptResult<PaginatedList<Department>>.SuccessAsync(pagedDepartments, Messages.Successfull);
}
}
}
public class RedisCacheService : IRedisCacheService
{
private readonly IDatabase _database;
private readonly IConnectionMultiplexer _connectionMultiplexer;
public RedisCacheService(IConnectionMultiplexer connectionMultiplexer)
{
_database = connectionMultiplexer.GetDatabase();
_connectionMultiplexer = connectionMultiplexer;
}
public bool IsConnected => _connectionMultiplexer.IsConnected;
public async Task<PaginatedList<T>> GetPaginatedListAsync<T>(string cachePrefixName, int pageIndex, Func<int, Task<PaginatedList<T>>> data) where T : class
{
int minPageIndex = pageIndex;
int maxPageIndex = pageIndex + 2;
if (pageIndex >= 1)
{
int prevMaxPageIndex = minPageIndex - 1;
int prevMinPageIndex = minPageIndex - 2;
for (int i = prevMinPageIndex; i <= prevMaxPageIndex; i++)
{
if (i >= 1)
{
string oldPageKey = $"{cachePrefixName}_{i}";
await DeleteAsync(oldPageKey);
}
}
}
for (int i = minPageIndex; i <= maxPageIndex; i++)
{
string pageKey = $"{cachePrefixName}_{i}";
var cachedData = await GetAsync<PaginatedList<T>>(pageKey);
if (cachedData == null)
{
var pageData = await data(i);
if (pageData.Data.Count > 0)
{
await SetAsync(pageKey, pageData, TimeSpan.FromHours(3));
}
}
}
string currentPageKey = $"{cachePrefixName}_{pageIndex}";
return await GetAsync<PaginatedList<T>>(currentPageKey);
}