Note: see the link below for the English version of this article.
https://duongnt.com/sliding-expiration-stackexchangeredis
Đây là bài đặc biệt trong loạt bài về distributed caching sử dụng Redis. Trong bài này, chúng ta sẽ tìm hiểu cách StackExchangeRedis tích hợp chức năng sliding expiration với Redis. Xin xem trang này để lấy đường link tới các phần còn lại.
Khái quát về expiration trong Redis
Như đã nói đến trong các bài trước, ta sử dụng lệnh EXPIRE để đặt thời hạn giá trị cache hết hạn.
EXPIRE <cache key> <X>
Lệnh này sẽ xóa giá trị khỏi cache sau khi X
giây trôi qua.
Ngoài ra, chúng ta có thể dùng lệnh EXPIREAT để xóa giá trị cache tại một thời điểm cố định.
EXPIREAT <cache key> <X>
Ở đây, X
có dạng Unix timestamp, với giá trị là số giây tính từ thời điểm 1970/01/01 00:00:00
.
Mỗi khi giá trị trong cache được ghi mới/cập nhật
Như đã nói trong phần 2, đây là đoạn script StackExchangeRedis sử dụng để ghi giá trị vào Redis.
@"
redis.call('HSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
return 1"
Có thể thấy là ngoài giá trị cache, giá trị absolute expiration cũng được ghi vào hash với tên trường là absexp
. Và giá trị sliding expiration được ghi vào hash với trên trường là sldexp
. Để hiểu StackExchangeRedis sử dụng 2 trường đó như thế nào, chúng ta cần xem cách StackExchangeRedis gọi đoạn script.
await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
value
}).ConfigureAwait(false);
Khi giá trị ARGV[3] != -1
, lệnh EXPIRE
được gọi với tham số ARGV[3]
. Giá trị của ARGV[3]
được tính bởi hàm GetExpirationInSeconds.
return (long)Math.Min(
(absoluteExpiration.Value - creationTime).TotalSeconds,
options.SlidingExpiration.Value.TotalSeconds);
StackExchangeRedis sẽ tính số giây giữa thời điểm hiện tại và absolute expiration, rồi so sánh với giá trị của sliding expiration. Giá trị nào bé hơn sẽ được dùng để đặt thời điểm xóa cache. Điều này cũng giải thích vì sao sliding expiration không thể giữ một giá trị trong cache quá thời điểm quy định bởi absolute expiration.
Cách implement chức năng sliding expiration
Từ cơ chế hoạt động của Redis, có thể thấy rằng StackExchangeRedis phải tự thực hiện một số bước để cập nhật lại thời điểm cache hết hạn mỗi khi ta truy xuất giá trị cache đó. Có 2 cách truy xuất một giá trị cache. Ta có thể đọc giá trị cache bằng hàm GetAsync; hoặc chỉ cập nhật lại sliding expiration mà không đọc giá trị bằng hàm RefreshAsync.
Trong cả 2 trường hợp, ta đều gọi đến hàm GetAndRefreshAsync. Điểm khác biệt duy nhất là ở chỗ ta đặt giá trị getData
là true
hay false
.
Nếu như giá trị cache có tồn tại, ta sẽ đọc cả giá trị absolute expiration và sliding expiration của nó. Rồi ta sử dụng chúng để gọi bản private của hàm RefreshAsync
.
if (results.Length >= 2)
{
MapMetadata(results, out DateTimeOffset? absExpr, out TimeSpan? sldExpr);
await RefreshAsync(key, absExpr, sldExpr, token).ConfigureAwait(false);
}
Trong phiên bản private này, ta gọi lệnh EXPIRE
để cập nhật thời điểm xóa giá trị cache khỏi Redis (nếu cần thiết). Chú ý là ở đây có bước kiểm tra xem sliding expiration có khiến giá trị cache tồn tại quá thời điểm absolute expiration hay không.
if (sldExpr.HasValue)
{
if (absExpr.HasValue)
{
var relExpr = absExpr.Value - DateTimeOffset.Now;
expr = relExpr <= sldExpr.Value ? relExpr : sldExpr;
}
else
{
expr = sldExpr;
}
await _cache.KeyExpireAsync(_instance + key, expr).ConfigureAwait(false);
// TODO: Error handling
}
Và đó là toàn bộ cách StackExchangeRedis viết chức năng sliding expiration.
Kết thúc
Việc tìm hiểu cách StackExchangeRedis viết chức năng sliding expiration giúp chúng ta hiểu rõ hơn cách Redis hoạt động. Tôi cũng hy vọng là tác giả của StackExchangeRedis sẽ sớm có thời gian để thực hiện nốt đoạn TODO: Error handling
:).
One Thought on “Sliding expiration trong StackExchangeRedis”