Điều này dẫn đến điểm đầu tiên tôi muốn thảo luận - tỷ số bóng đá pháp

/imgposts/3g91z1af.jpg

Bài viết này không còn giới thiệu về cấu trúc dữ liệu nữa. Các cấu trúc dữ liệu cơ bản đã được trình bày trong các bài trước. Bài này tập trung vào việc bổ sung những khái niệm quan trọng nhưng có thể bị bỏ qua, hoặc là điều mà nhiều bài viết về Redis thường không đề cập đến. Khi học Redis, người ta thường nghĩ rằng chỉ cần hiểu rõ các cấu trúc dữ liệu chính và "thuật toán" là đủ.

Redis chủ yếu được sử dụng như một bộ nhớ đệm hiệu năng cao. Vậy khi sử dụng bộ nhớ đệm, cần lưu ý gì? Trước tiên là tốc độ truy cập. Nếu thời gian lấy dữ liệu từ bộ nhớ đệm bằng với thời gian từ cơ sở dữ liệu, thì bộ nhớ đệm sẽ mất đi ý nghĩa tồn tại của nó. Thứ hai là ý nghĩa thực sự của bộ nhớ đệm: chúng ta chỉ muốn làm cho việc đọc dữ liệu nhanh hơn. Trong hầu hết các trường hợp, dữ liệu cần phải được cập nhật hoặc hết hạn theo thời gian nhất định. Điều này dẫn đến điểm đầu tiên tôi muốn thảo luận.

Redis xử lý cách nào để hết hạn bộ nhớ đệm? Có thể suy đoán rằng mỗi key có thời gian hết hạn đều đặt một bộ đếm thời gian, khi hết hạn thì xóa key đó. Cách này chắc chắn tiêu tốn tài nguyên rất lớn, nhưng lại dọn dẹp kịp thời nhất. Ngoài ra, Redis áp dụng chiến lược dọn dẹp lười biếng (lazy) và kiểm tra định kỳ. Cụ thể, khi sử dụng key, Redis sẽ kiểm tra xem key đó đã hết hạn chưa. Ví dụ, khi thực hiện lệnh get, Redis sẽ kiểm tra key có còn thời hạn hay không. Nếu hết hạn, key sẽ bị xóa và trả về rỗng; nếu chưa hết hạn, dữ liệu sẽ được trả về bình thường. Đoạn mã chính liên quan đến quá trình này như sau:

 1/* Hàm này được gọi khi chúng ta chuẩn bị thực hiện một số thao tác trên key nào đó,
 2   nhưng key đó có thể đã hết hạn ngay cả khi nó vẫn tồn tại trong cơ sở dữ liệu.
 3   Cách hoạt động của hàm này phụ thuộc vào vai trò của máy chủ Redis.
 4   Slave không tự động hết hạn key mà chờ đợi DEL từ Master để đảm bảo tính nhất quán.
 5   Tuy nhiên, slave vẫn cố gắng trả về thông tin chính xác cho người gọi, 
 6   tức là 0 nếu key vẫn còn giá trị và 1 nếu key đã hết hạn. */
 7int expireIfNeeded(redisDb *db, robj *key) {
 8    if (!keyIsExpired(db,key)) return 0;
 9    
10    /* Nếu chạy trong ngữ cảnh của slave, thay vì xóa key hết hạn khỏi cơ sở dữ liệu,
11       chúng ta trả về sớm. Việc hết hạn key ở slave do master kiểm soát bằng cách gửi DEL. */
12    if (server.masterhost != NULL) return 1;
13
14    /* Xóa key */
15    server.stat_expiredkeys++;
16    propagateExpire(db,key,server.lazyfree_lazy_expire);
17    notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired",key,db->id);
18    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) : dbSyncDelete(db,key);
19}

Một số điểm cần lưu ý:

  • Khi xóa lười biếng (lazy delete), Redis sẽ quyết định xóa đồng bộ hay bất đồng bộ dựa trên tham số lazyfree_lazy_expire.
  • Đối với slave, không cần thực hiện việc hết hạn vì master sẽ gửi lệnh DEL khi key hết hạn.

Tuy nhiên, nếu chỉ áp dụng chiến lược này, sẽ xảy ra vấn đề gì? Giả sử một số key không bao giờ được truy cập, thì những key này sẽ không bao giờ hết hạn, dẫn đến việc chiếm dụng bộ nhớ không cần thiết. Do đó, Redis kết hợp chiến lược hết hạn lười biếng với chiến lược hết hạn định kỳ. Vậy chiến lược định kỳ hoạt động như thế nào?

 1/* Hàm này xử lý các thao tác 'nền' mà chúng ta cần thực hiện dần dần trong các cơ sở dữ liệu Redis,
 2   chẳng hạn như hết hạn key, tăng kích thước bảng hash, hoặc tái phân phối. */
 3void databasesCron(void) {
 4    /* Kiểm tra và hết hạn key bằng cách chọn mẫu ngẫu nhiên. Không yêu cầu đối với slave
 5       vì master sẽ gửi DEL cho slave. */
 6    if (server.active_expire_enabled) {
 7        if (server.masterhost == NULL) {
 8            activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
 9        } else {
10            expireSlaveKeys();
11        }
12    }
13
14    /* Phân mảnh key dần dần. */ [tỷ số bóng đá pháp](/blog/87e07cd63ec6e2ed.html) 
15    activeDefragCycle();
16
17    /* Thực hiện tái phân phối bảng hash nếu cần thiết, nhưng chỉ khi không có tiến trình con nào đang hoạt động. */
18    if (!hasActiveChildProcess()) {
19        static unsigned int resize_db = 0;
20        static unsigned int rehash_db = 0;
21        int dbs_per_call = CRON_DBS_PER_CALL;
22        int j;
23
24        for (j = 0; j < dbs_per_call; j++) {
25            tryResizeHashTables(resize_db % server.dbnum);
26            resize_db++;
27        }
28
29        if (server.activerehashing) {
30            for (j = 0; j < dbs_per_call; j++) {
31                int work_done = incrementallyRehash(rehash_db);
32                if (work_done) {
33                    break;
34                } else {
35                    rehash_db++;
36                    rehash_db %= server.dbnum;
37                }
38            }
39        }
40    }
41}
42
43/* Thử hết hạn một số key đã hết thời gian. Thuật toán sử dụng thích ứng và sẽ sử dụng ít tài nguyên CPU nếu có ít key hết hạn,
44   ngược lại sẽ tích cực hơn để tránh tình trạng sử dụng quá nhiều bộ nhớ bởi các key có thể loại bỏ khỏi không gian key. */
45void activeExpireCycle(int type) {
46    // ... [Chi tiết code đã được mô tả bên trên] ...
47}

Quá trình hết hạn định kỳ chia thành hai loại: nhanh và chậm. Loại nhanh được gọi từ hàm beforeSleep, còn loại chậm từ hàm databasesCron. Phiên bản nhanh có hai hạn chế:

  • Thời gian thực thi bị giới hạn bởi ACTIVE_EXPIRE_CYCLE_FAST_DURATION.
  • Khoảng cách giữa các lần thực thi là gấp đôi ACTIVE_EXPIRE_CYCLE_FAST_DURATION.

Ngoài ra, hành vi này cũng có thể được điều chỉnh bởi tham số cấu hình server.active_expire_effort, mặc định là 1 và 22win casino tối đa là 10.

Tiếp theo, Redis sẽ chọn một số lượng nhất định key có thời gian hết hạn từ các database (lưu trữ trong expires). Số lượng key này được kiểm soát bởi:

1config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP + 
2                      ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort;

Thời gian thực thi của phiên bản chậm được tính như sau:

1config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC + 
2                              2*effort;
3timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;

Cuối cùng, có thêm một điều kiện thoát đặc biệt: nếu tỷ lệ key hết hạn trong mẫu của database hiện tại đạt đến mức cho phép, Redis sẽ không tiếp tục xử lý database hiện tại mà chuyển sang database tiếp theo.

Hy vọng bài viết này giúp bạn hiểu 2bet rõ hơn về cách Redis quản lý hết hạn key!