Trang chủ > Công nghệ máy chủ > Nội dung chính

Phân tích sâu cấu trúc dữ liệu nội bộ của Redis (3) —— robj


Phân tích cấu trúc dữ liệu nội bộ của Redis Phân tích cấu trúc dữ liệu nội bộ của Redis Bài thứ ba trong series nàykeo 88, nói về một cấu trúc dữ liệu cơ bản trong việc triển khai Redis: robj.

Vậy rốt cuộc điều gì là robj? Nó có tác dụng gì?

Từ góc độ người sử dụng Redis99WIN, một nút Redis có thể chứa nhiều database (trong chế độ không cluster, mặc định là 16 database, còn trong chế độ cluster thì chỉ có thể là 1 database), và mỗi database quản lý mối quan hệ ánh xạ giữa không gian key và khô Trong đó, key của mối quan hệ này luôn ở dạng chuỗi (string), còn giá trị (value) có thể thuộc nhiều loại khác nhau, chẳng hạn như chuỗi (string), danh sách (list), bản đồ (hash), v.v. Điều này cho thấy rằng, kiểu dữ liệu của key luôn cố định là chuỗi (string), trong khi giá trị (value) có thể linh hoạt hơn với nhiều định dạng khác nhau. Ngoài ra, điều thú vị là Redis được thiết kế để tối ưu hóa việc lưu trữ và truy xuất dữ liệu dựa trên cấu trúc key-value này. Với sự đa dạng về kiểu dữ liệu mà nó hỗ trợ, Redis trở thành một công cụ mạnh mẽ cho việc lưu trữ và xử lý dữ liệu linh hoạt, đáp ứng yêu cầu của nhiều ứng dụng hiện đại. Đặc biệt, nhờ khả năng mở rộng thông qua cluster, người dùng có thể phân chia dữ liệu một cách hiệu quả, giảm tải và cải thiện hiệu suất tổng thể của hệ thống.

Từ góc độ thực hiện nội bộ của Redis99WIN, trong bài đầu tiên của loạt bài viết này, chúng ta đã đề cập rằng mối quan hệ ánh xạ trong một database được duy trì bằng cách sử dụng một dict. Đối với key của dict, chỉ cần sử dụng một cấu trúc dữ liệu cụ thể là đủ, và điều đó chính là chuỗi động (dynamic string - sds). Còn đối với value, lại phức tạp hơn nhiều. Để có thể lưu trữ các giá trị thuộc kiểu khác nhau trong cùng một dict, cần phải có một cấu trúc dữ liệu chung. Cấu trúc dữ liệu này được gọi là robj (tên đầy đủ là redisObject). Ví dụ: nếu value là một danh sách (list), thì cấu trúc lưu trữ bên trong sẽ là một quicklist (cách thực hiện chi tiết của quicklist sẽ được thảo luận trong các bài viết sau). Nếu value là một chuỗi (string), thì cấu trúc lưu trữ bên trong thường là một sds. Tuy nhiên, thực tế còn phức tạp hơn chút nữa. Ví dụ, nếu một value kiểu string có giá trị là một số, thì Redis sẽ chuyển đổi nó thành kiểu long để giảm thiểu việc sử dụng bộ nhớ. Và một robj không chỉ có thể biểu diễn một sds hoặc một quicklist mà thậm chí còn có thể biểu diễn cả kiểu long. Cấu trúc robj đóng vai trò như một "trung tâm" cho phép Redis linh hoạt quản lý nhiều loại dữ liệu khác nhau trong cùng một hệ thống, giúp tối ưu hóa hiệu suất và sử dụng tài nguyên. Điều này cũng cho thấy sự tinh vi trong thiết kế của Redis, khi nó có thể xử lý đa dạng các loại dữ liệu mà không làm phức tạp hóa quá mức cấu trúc cơ bản của mình.

Định nghĩa cấu trúc dữ liệu robj

htỷ lệ kèo bóng đá trực tiếp, chúng ta có thể tìm thấy mã liên quan đến định nghĩa của robj như bên dưới (các đoạn code trong loạt bài viết này đều được lấy từ nhánh 3.2 của mã nguồn Redis): ```c typedef struct redisObject { unsigned type:4; // Loại đối tượng unsigned encoding:4; // Cách mã hóa dữ liệu unsigned lru:22; // Thời gian truy cập gần nhất void *ptr; // Trỏ đến dữ liệu thực tế } robj; ``` Đây là cấu trúc cơ bản của đối tượng trong Redis, giúp tối ưu hóa hiệu suất và khả năng quản lý bộ nhớ.

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
													
														/* Object types */
													
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

													
/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
													
#define OBJ_ENCODING_RAW 0     
													/* Raw representation */
													
#define OBJ_ENCODING_INT 1     
													/* Encoded as integer */
													
#define OBJ_ENCODING_HT 2      
													/* Encoded as hash table */
													
#define OBJ_ENCODING_ZIPMAP 3  
													/* Encoded as zipmap */
													
#define OBJ_ENCODING_LINKEDLIST 4 
													/* Encoded as regular linked list */
													
#define OBJ_ENCODING_ZIPLIST 5 
													/* Encoded as ziplist */
													
#define OBJ_ENCODING_INTSET 6  
													/* Encoded as intset */
													
#define OBJ_ENCODING_SKIPLIST 7  
													/* Encoded as skiplist */
													
#define OBJ_ENCODING_EMBSTR 8  
													/* Embedded sds string encoding */
													
#define OBJ_ENCODING_QUICKLIST 9 
													/* Encoded as linked list of ziplists */
													

													
#define LRU_BITS 24

													typedef
													 struct
													 redisObject
													 {
													
    unsigned
													 type
													:
													4
													;
													
    unsigned
													 encoding
													:
													4
													;
													
    unsigned
													 lru
													:
													LRU_BITS
													;
													 /* lru time (relative to server.lruclock) */
													
    int
													 refcount
													;
													
    void
													 *
													ptr
													;
													
}
													 robj
													;
													

Một robj bao gồm 5 trường sau đây:

  • Loại dữ liệu của đối tượng: Đây là kiểu dữ liệu99WIN, chiếm 4 bit. Có thể có 5 giá trị khác nhau: OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET và OBJ_HASH, tương ứng với 5 loại cấu trúc dữ liệu mà Redis cung cấp cho người dùng (tức là 5 loại cấu trúc dữ liệu ở tầng đầu tiên mà chúng ta đã đề cập trong bài viết đầu tiên). Mỗi loại đều đại diện cho một cách tổ chức dữ liệu khác nhau, cho phép người dùng lưu trữ và thao tác dữ liệu theo cách linh hoạt tùy thuộc vào nhu cầu cụ thể.
  • Phương thức mã hóa: Cách biểu diễn nội bộ của đối tượng (cũng có thể được gọi là mã hóa). Nó chiếm 4 bit. Có thể có 10 giá trị khác nhau99WIN, tương ứng với 10 hằng số OBJ_ENCODING_XXX trong mã nguồn trước đây. Những hằng số này đại diện cho các phương thức mã hóa khác nhau mà hệ thống sử dụng để tối ưu hóa không gian lưu trữ và hiệu suất xử lý của đối tượng.
  • LRU: Dùng để thực hiện thuật toán thay thế LRU99WIN, chiếm 24 bit. Điều này không phải là trọng tâm trong phần thảo luận của chúng ta ở đây, vì vậy hãy tạm thời bỏ qua nó.
  • refcount: Số lượng tham chiếu. Nó cho phép đối tượng robj được chia sẻ trong một số tình huống.
  • Con trỏ ptr: Đây là con trỏ dữ liệutỷ lệ kèo bóng đá trực tiếp, dẫn đến nơi thực sự lưu trữ thông tin. Ví dụ, đối với một robj đại diện cho chuỗi (string), con trỏ ptr của nó có thể hướng tới một cấu trúc sds; còn đối với một robj đại diện cho danh sách (list), con trỏ ptr của nó có thể dẫn đế Điều này cho phép các đối tượng có thể linh hoạt lưu trữ và thao tác với nhiều loại dữ liệu khác nhau trong hệ thống.

Điều đặc biệt quan trọng cần chú ý ở đây chính là trườ Đối với cùng một type99WIN, có thể sẽ tồn tại nhiều giá trị encoding khác nhau, điều này cho thấy rằng cùng một loại dữ liệu, có thể được biểu diễn theo nhiều cách nội bộ khác nhau. Và mỗi phương thức biểu diễn nội bộ này sẽ ảnh hưởng đến việc sử dụng bộ nhớ cũng như hiệu suất tìm kiếm.

Ví dụ99WIN, khi type = OBJ_STRING, điều này cho thấy rằng robj đang lưu trữ một chuỗi và encoding ở thời điểm này có thể là một trong ba tùy chọn sau đây:

  • Với OBJ_ENCODING_RAWtỷ lệ kèo bóng đá trực tiếp, chuỗi sẽ được biểu diễn ở dạng gốc, cụ thể là sử dụng sds (simple dynamic string) để lưu trữ và quản lý dữ liệu. Đây là cách trực tiếp nhất để lưu trữ chuỗi mà không có bất kỳ sự mã hóa hay nén nào thêm vào.
  • Với OBJ_ENCODING_INTkeo 88, chuỗi được biểu diễn dưới dạng số nguyên, thực chất là kiểu dữ liệu long. Đây là một cách lưu trữ tối ưu khi giá trị trong chuỗi chỉ bao gồm các con số và không cần xử lý như một chuỗi ký tự thông thường. Điều này giúp tiết kiệm bộ nhớ cũng như tăng tốc độ truy xuất thông tin vì máy tính có thể xử lý số nguyên nhanh hơn nhiều so với chuỗi văn bản.
  • Với OBJ_ENCODING_EMBSTR99WIN, chuỗi được biểu diễn bằng một loại sds (simple dynamic string) đặc biệt được tích hợp sẵn. Đây là một phương pháp tối ưu hóa hiệu quả cho các chuỗi ngắn thường xuyên được sử dụng. Chúng ta sẽ đi sâu vào chi tiết về cách thức hoạt động của nó trong phần thảo luận tiếp theo.

Hãy lấy một ví dụ khác: Khi type được thiết lập thành OBJ_HASHkeo 88, điều đó có nghĩa là đối tượng robj đang lưu trữ một hash. Ở trường hợp này, encoding có thể chọn một trong hai phương thức sau: 1. **Encoding dạng bảng băm (hashtable)**: Đây là cách mà dữ liệu được tổ chức dưới dạng một bảng băm với các cặp khóa-giá trị, cho phép truy xuất nhanh chóng và hiệu quả. 2. **Encoding dạng danh sách liên kết đôi (linked list)**: Trong trường hợp này, dữ liệu sẽ được lưu trữ dưới dạng một danh sách liên kết đôi, nơi mỗi phần tử giữ tham chiếu đến phần tử trước và sau nó. Lựa chọn encoding phụ thuộc vào đặc điểm cụ thể của dữ liệu cũng như yêu cầu về hiệu suất truy vấn và bộ nhớ.

  • OBJ_ENCODING_HT: Hash sử dụng một dict để biểu diễn.
  • Với OBJ_ENCODING_ZIPLISTtỷ lệ kèo bóng đá trực tiếp, kiểu hash sẽ được biểu diễn bằng một ziplist (cách thực hiện cụ thể của ziplist chúng ta sẽ chi tiết trong các bài viết sau). Ziplist là một cấu trúc dữ liệu đặc biệt giúp lưu trữ các cặp khóa-giá trị một cách hiệu quả, tiết kiệm bộ nhớ và dễ dàng quản lý khi kích thước dữ liệu không quá lớn.

Phần còn lại của bài viết này sẽ tập trung vào đối tượng robjkeo 88, đại diện cho chuỗi string, và đi sâu phân tích ba cách mã hóa khác nhau mà nó có thể sử dụng. Trong đoạn mã trước đó, chúng ta đã thấy mười loại mã hóa khác nhau xuất hiện; ở đây, chúng ta sẽ giải thích sơ lược về chúng, và trong các bài viết tiếp theo của loạt bài này, chắc chắn sẽ có thêm nhiều cơ hội để tìm hiểu kỹ hơn về những mã hóa này. Mỗi loại mã hóa đều đóng vai trò quan trọng trong việc tối ưu hóa hiệu suất và không gian lưu trữ dữ liệu, giúp Redis hoạt động một cách linh hoạt và hiệu quả nhất có thể. Chúng tôi sẽ lần lượt khám phá từng loại mã hóa, từ cách chúng hoạt động đến lý do tại sao Redis chọn mã hóa cụ thể nào trong từng trường hợp. Điều này không chỉ giúp bạn hiểu rõ hơn về cách hệ thống hoạt động mà còn cung cấp cho bạn kiến thức nền tảng cần thiết để tối ưu hóa ứng dụng của mình với Redis.

  • Với OBJ_ENCODING_RAW99WIN, đây là cách biểu diễn thuần túy nhất. Thực tế, chỉ có kiểu dữ liệu string mới sử dụng giá trị mã hóa này (được biểu thị dưới dạng sds). Nói cách khác, OBJ_ENCODING_RAW không thực hiện bất kỳ thao tác nén hay tối ưu hóa nào mà giữ nguyên trạng thái gốc của chuỗi, giúp duy trì sự đơn giản và hiệu quả trong việc xử lý dữ liệu cơ bản. Điều này đặc biệt hữu ích khi bạn cần đảm bảo rằng chuỗi được lưu trữ một cách trung thực mà không bị thay đổi hay biến dạng bởi bất kỳ thuật toán phức tạp nào.
  • OBJ_ENCODING_INT: Biểu diễn dưới dạng số. Thực tế sử dụng long để biểu thị.
  • OBJ_ENCODING_HT: Biểu diễn dưới dạng dict.
  • OBJ_ENCODING_ZIPMAP: Đây là một định dạng mã hóa cũ và đã bị ngừng sử dụng. Bạn chỉ có thể tìm thấy nó trong các phiên bản Redis nhỏ hơn 2.699WIN, khi mà hệ thống này vẫn đang trong giai đoạn phát triển và tối ưu hóa.
  • OBJ_ENCODING_LINKEDLIST: Đây là một phương pháp biểu diễn cũkeo 88, hiện không còn sử dụng.
  • OBJ_ENCODING_ZIPLIST: Biểu diễn dưới dạ
  • OBJ_ENCODING_INTSET: biểu thị dưới dạng intset. Đây là một loại mã hóa được sử dụng cho cấu trúc dữ liệu set99WIN, giúp lưu trữ các phần tử số nguyên một cách hiệu quả
  • OBJ_ENCODING_SKIPLIST: được biểu thị dưới dạng danh sách nhảy (skiplist). Đây là phương thức được sử dụng trong cấu trúc dữ liệu tập hợp đã sắp xếp (sorted set)tỷ lệ kèo bóng đá trực tiếp, giúp tối ưu hóa việc tìm kiếm và thao tác với các phần tử một cách hiệu quả.
  • OBJ_ENCODING_EMBSTR: Biểu diễn dưới dạng một loại sds nhúng đặc biệt.
  • OBJ_ENCODING_QUICKLIST: đại diệ Được sử dụng cho cấu trúc dữ liệu list. Quicklist là sự kết hợp giữa các nút list và phần tử ziplisttỷ lệ kèo bóng đá trực tiếp, giúp tối ưu hóa bộ nhớ và tăng tốc độ truy xuất khi xử lý các danh sách có kích thước lớn trong hệ thống.

Chúng ta hãy tóm tắt lại vai trò của robj:

  • Cung cấp một cách thống nhất để biểu diễn nhiều loại dữ liệu.
  • Cho phép cùng một loại dữ liệu sử dụng các biểu diễn nội bộ khác nhautỷ lệ kèo bóng đá trực tiếp, nhờ đó tiết kiệm bộ nhớ trong một số trường hợp.
  • Hỗ trợ chia sẻ đối tượng và đếm tham chiếu. Khi đối tượng được chia sẻkeo 88, chỉ chiếm một bản sao bộ nhớ, từ đó tiết kiệm thêm bộ nhớ.

Quy trình mã hóa của string robj

Khi thực hiện lệnh set trong Redistỷ lệ kèo bóng đá trực tiếp, Redis sẽ đầu tiên biểu diễn giá trị value (kiểu chuỗi) mà nó nhận được thành một đối tượng robj với type = OBJ_STRING và encoding = OBJ_ENCODING_RAW. Sau đó, trước khi lưu vào bộ nhớ nội bộ, nó sẽ tiến hành một quá trình mã hóa nhằm cố gắng biểu diễn giá trị này theo một định dạng mã hóa khác tiết kiệm bộ nhớ hơn. Phần lõi của quy trình này nằm trong hà c. Trong quá trình này, Redis không chỉ đơn giản lưu trữ dữ liệu mà còn thực hiện các thao tác tối ưu hóa để đảm bảo hiệu suất cao nhất có thể. Điều này giúp giảm thiểu việc sử dụng tài nguyên hệ thống, đặc biệt là trong các ứng dụng lớn với khối lượng dữ liệu khổng lồ. Những cải tiến về mã hóa này cũng cho phép Redis xử lý nhiều yêu cầu cùng lúc mà vẫn duy trì tốc độ phản hồi nhanh chóng, từ đó nâng cao trải nghiệm người dùng tổng thể.

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
													
														robj
													 *
													tryObjectEncoding
													(
													robj
													 *
													o
													)
													 {
													
    long
													 value
													;
													
    sds
													 s
													 =
													 o
													->
													ptr
													;
													
    size_t
													 len
													;
													

    /* Make sure this is a string objecttỷ lệ kèo bóng đá trực tiếp, the only type we encode
     * in this function. Other types use encoded memory efficient
     * representations but are handled by the commands implementing
     * the type. */
    serverAssertWithInfo
													(
													NULL
													,
													o
													,
													o
													->
													type
													 ==
													 OBJ_STRING
													);
													

    /* We try some specialized encoding only for objects that are
     * RAW or EMBSTR encoded99WIN, in other words objects that are still
     * in represented by an actually array of chars. */
    if
													 (
													!
													sdsEncodedObject
													(
													o
													))
													 return
													 o
													;
													

    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace. */
													
     if
													 (
													o
													->
													refcount
													 >
													 1
													)
													 return
													 o
													;
													

    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 21 chars is not
     * representable as a 32 nor 64 bit integer. */
													
    len
													 =
													 sdslen
													(
													s
													);
													
    if
													 (
													len
													 <=
													 21
													 &&
													 string2l
													(
													s
													,
													len
													,
													&
													value
													))
													 {
													
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well. */
													
        if
													 ((
													server
													.
													maxmemory
													 ==
													 0
													 ||
													
             (
													server
													.
													maxmemory_policy
													 !=
													 MAXMEMORY_VOLATILE_LRU
													 &&
													
              server
													.
													maxmemory_policy
													 !=
													 MAXMEMORY_ALLKEYS_LRU
													))
													 &&
													
            value
													 >=
													 0
													 &&
													
            value
													 <
													 OBJ_SHARED_INTEGERS
													)
													
        {
													
            decrRefCount
													(
													o
													);
													
            incrRefCount
													(
													shared
													.
													integers
													[
													value
													]);
													
            return
													 shared
													.
													integers
													[
													value
													];
													
        }
													 else
													 {
													
            if
													 (
													o
													->
													encoding
													 ==
													 OBJ_ENCODING_RAW
													)
													 sdsfree
													(
													o
													->
													ptr
													);
													
            o
													->
													encoding
													 =
													 OBJ_ENCODING_INT
													;
													
            o
													->
													ptr
													 =
													 (
													void
													*
													)
													 value
													;
													
            return
													 o
													;
													
        }
													
    }
													

    /* If the string is small and is still RAW encodedtỷ lệ kèo bóng đá trực tiếp,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses. */
    if
													 (
													len
													 <=
													 OBJ_ENCODING_EMBSTR_SIZE_LIMIT
													)
													 {
													
        robj
													 *
													emb
													;
													

        if
													 (
													o
													->
													encoding
													 ==
													 OBJ_ENCODING_EMBSTR
													)
													 return
													 o
													;
													
        emb
													 =
													 createEmbeddedStringObject
													(
													s
													,
													sdslen
													(
													s
													));
													
        decrRefCount
													(
													o
													);
													
        return
													 emb
													;
													
    }
													

    /* We can't encode the object...
     *
     * Do the last trytỷ lệ kèo bóng đá trực tiếp, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
    if
													 (
													o
													->
													encoding
													 ==
													 OBJ_ENCODING_RAW
													 &&
													
        sdsavail
													(
													s
													)
													 >
													 len
													/
													10
													)
													
    {
													
        o
													->
													ptr
													 =
													 sdsRemoveFreeSpace
													(
													o
													->
													ptr
													);
													
    }
													

    /* Return the original object. */
													
    return
													 o
													;
													
}
													

Đoạn mã này thực hiện các bước khá phức tạptỷ lệ kèo bóng đá trực tiếp, chúng ta cần xem xét cẩn thận từng bước hoạt động:

  • Bước 1 kiểm tra99WIN, kiể Đảm bảo chỉ thực hiện thao tác trên các đối tượng kiểu chuỗi.
  • Bước kiểm tra thứ haikeo 88, hãy đảm bảo rằng bạn đang kiểm tra đúng định dạng mã hóa (encoding). Macro sdsEncodedObject được định nghĩa trong tệp server.h và nó chỉ áp dụng cho các đối tượng chuỗi (string) có mã hóa là OBJ_ENCODING_RAW hoặc OBJ_ENCODING_EMBSTR. Cả hai loại mã hóa này đều sử dụng kiểu dữ liệu sds để lưu trữ thông tin, do đó, bạn có thể cân nhắc thực hiện thêm các bước mã hóa bổ sung tùy theo yêu cầu cụ thể của ứng dụng. Điều quan trọng là phải hiểu rõ đặc điểm của từng loại mã hóa để có thể xử lý dữ liệu một cách hiệu quả nhất.
							
								
									#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
								
							
  • Bước kiểm tra thứ bakeo 88, chúng ta sẽ kiểm tra tham chiế Đối tượng được chia sẻ có tham chiếu lớn hơn 1 sẽ được sử dụng tại nhiều vị trí khác nhau. Khi quá trình mã hóa kết thúc, con trỏ đối tượng của robj có thể thay đổi (chúng ta đã đề cập đến kiểu mẫu sử dụng này khi giải thích về hàm sdscatlen trong bài trước). Do đó, đối với các đối tượng có tham chiếu lớn hơn 1, việc cập nhật tất cả các vị trí tham chiếu là rất khó thực hiện. Vì vậy, đối với các đối tượng có giá trị tham chiếu lớn hơn 1, chúng ta không tiến hành xử lý mã hóa.
  • Bạn có thể thử chuyển đổi chuỗi thành số nguyên dài 64 bit (long). Phạm vi giá trị mà số nguyên dài 64 bit có thể biểu diễn là từ -2^63 đến 2^63 - 199WIN, khi viết dưới dạng thập phân thì độ dài tối đa của nó là 20 chữ số (bao gồm dấu trừ). Ở đây, việc kiểm tra điều kiện nhỏ hơn hoặc bằng 21 dường như là thừa thãi, thực tế chỉ cần kiểm tra nhỏ hơn hoặc bằng 20 là đủ (nếu tôi tính nhầm, xin hãy chỉ cho tôi ngay nhé). Nếu hàm string2l thành công trong việc chuyển đổi chuỗi thành long, nó sẽ trả về giá trị 1 và lưu kết quả chuyển đổi vào biến value.
  • Khi chuyển đổi thành long thành côngkeo 88, sẽ chia ra hai trường hợp.
    • Trường hợp đầu tiên: Nếu cấu hình Redis không yêu cầu sử dụng thuật toán thay thế LRU và giá trị số dạng long được chuyển đổi có kích thước nhỏ (dưới OBJ_SHARED_INTEGERSkeo 88, hiện tại giá trị này là 10.000), thì sẽ sử dụng các đối tượng số chia sẻ để biểu diễn. Lý do việc kiểm tra ở đây liên quan đến LRU là vì thuật toán LRU yêu cầu mỗi đối tượng robj phải có giá trị lru field khác nhau, vì vậy khi áp dụng LRU, các robj không thể được chia sẻ. Mảng shared.integers có độ dài 10.000, trong đó đã lưu trữ trước 10.000 đối tượng số nhỏ. Những đối tượng số này đều là các đối tượng string robj với kiểu mã hóa encoding = OBJ_ENCODING_INT. Thêm vào đó, việc sử dụng các đối tượng số chia sẻ không chỉ giúp tối ưu hóa bộ nhớ mà còn tăng tốc độ truy cập dữ liệu. Khi một số nhỏ được yêu cầu, Redis sẽ kiểm tra xem nó có nằm trong mảng shared.integers hay không. Nếu có, nó sẽ trả về đối tượng đã tồn tại thay vì tạo một đối tượng mới, từ đó giảm thiểu tài nguyên và cải thiện hiệu suất tổng thể của hệ thống. Điều này đặc biệt hữu ích trong các trường hợp có nhiều yêu cầu lặp đi lặp lại cho cùng một tập hợp số nhỏ, chẳng hạn như trong các ứng dụng xử lý dữ liệu lớn hoặc phân tích thời gian thực.
    • Trường hợp thứ hai: Nếu bước trước đó không thể sử dụng đối tượng nhỏ chung để biểu diễnkeo 88, lúc này, robj ban đầu sẽ được mã hóa thành encoding = OBJ_ENCODING_INT. Ở thời điểm này, trường hợp ptr sẽ trực tiếp lưu trữ giá trị kiểu long. Lưu ý rằng ptr là một con trỏ void * (tức là nó lưu trữ địa chỉ bộ nhớ), do đó trên hệ thống máy tính 64-bit, nó có kích thước 64 bit, đủ để lưu trữ một giá trị long kiểu 64-bit. Bằng cách này, ngoài robj ra, không cần thêm không gian bộ nhớ nào khác để lưu trữ giá trị chuỗi. Điều này giúp tối ưu hóa bộ nhớ và cải thiện hiệu suất trong việc quản lý tài nguyên của ứng dụng.
  • Tiếp theo là xử lý các chuỗi không thể chuyển đổi thành long 64-bit. Cuối cùng thực hiện thêm hai bước xử lý:
    • Nếu độ dài chuỗi đủ nhỏ (nhỏ hơn hoặc bằng OBJ_ENCODING_EMBSTR_SIZE_LIMIT99WIN, được định nghĩa là 44), thì việc gọi createEmbeddedStringObject sẽ mã hóa thành encoding = OBJ_ENCODING_EMBSTR. Khi đó, bộ nhớ cho chuỗi sẽ được cấp phát trong vùng liên tục của bộ nhớ, giúp tối ưu hóa hiệu suất truy xuất và giảm thiểu chi phí quản lý bộ nhớ. Điều này đặc biệt hữu ích khi xử lý các chuỗi ngắn thường xuyên được sử dụng, vì nó cho phép lưu trữ toàn bộ dữ liệu chuỗi trong cùng một khối bộ nhớ, từ đó tăng tốc đáng kể quá trình thao tác với chuỗi.
    • Nếu tất cả các lần thử mã hóa trước đó đều thất bại (vẫn còn ở trạng thái OBJ_ENCODING_RAW) và trong sds có quá nhiều byte dư thừatỷ lệ kèo bóng đá trực tiếp, thì sẽ có một nỗ lực cuối cùng được thực hiện bằng cách gọi phương thức sdsRemoveFreeSpace của sds để giải phóng những byte dư thừa này. Điều này giúp tối ưu hóa không gian lưu trữ và tăng hiệu suất cho đối tượng khi không cần giữ lại phần nhớ chưa được sử dụng.

Trong đó99WIN, việc gọi hàm createEmbeddedStringObject, chúng ta nên dành chút thời gian để xem qua mã nguồn của nó: Hàm này có thể đóng vai trò quan trọng trong việc xử lý các chuỗi nhúng, cho phép chúng ta hiểu sâu hơn về cách mà hệ thống tạo và quản lý dữ liệu dạng chuỗi. Hãy cùng khám phá chi tiết bên trong hàm này để hiểu rõ hơn về chức năng cũng như cách thức hoạt động mà nó mang lại.

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
													
														robj
													 *
													createEmbeddedStringObject
													(
													const
													 char
													 *
													ptr
													,
													 size_t
													 len
													)
													 {
													
    robj
													 *
													o
													 =
													 zmalloc
													(
													sizeof
													(
													robj
													)
													+
													sizeof
													(
													struct
													 sdshdr8
													)
													+
													len
													+
													1
													);
													
    struct
													 sdshdr8
													 *
													sh
													 =
													 (
													void
													*
													)(
													o
													+
													1
													);
													

    o
													->
													type
													 =
													 OBJ_STRING
													;
													
    o
													->
													encoding
													 =
													 OBJ_ENCODING_EMBSTR
													;
													
    o
													->
													ptr
													 =
													 sh
													+
													1
													;
													
    o
													->
													refcount
													 =
													 1
													;
													
    o
													->
													lru
													 =
													 LRU_CLOCK
													();
													

    sh
													->
													len
													 =
													 len
													;
													
    sh
													->
													alloc
													 =
													 len
													;
													
    sh
													->
													flags
													 =
													 SDS_TYPE_8
													;
													
    if
													 (
													ptr
													)
													 {
													
        memcpy
													(
													sh
													->
													buf
													,
													ptr
													,
													len
													);
													
        sh
													->
													buf
													[
													len
													]
													 =
													 '\0'
													;
													
    }
													 else
													 {
													
        memset
													(
													sh
													->
													buf
													,
													0
													,
													len
													+
													1
													);
													
    }
													
    return
													 o
													;
													
}
													

Hàm createEmbeddedStringObject sẽ tái phân bổ bộ nhớ cho sds và đặt cả robj lẫn sds vào một khối bộ nhớ liên tục99WIN, giúp việc lưu trữ chuỗi ngắn trở nên hiệu quả hơn trong việc giảm thiểu các mảnh vụn bộ nhớ. Khối bộ nhớ liên tục này bao gồm các phần sau đây: 1. Phần đầu tiên là phần dành riêng cho cấu trúc dữ liệu robj, nơi lưu giữ thông tin cơ bản về đối tượng như kiểu dữ liệu và trạng thái. 2. Tiếp theo là vùng lưu trữ sds, nơi chứa chuỗi thực tế mà người dùng muốn lưu trữ. Kích thước của vùng này có thể thay đổi linh hoạt tùy thuộc vào độ dài chuỗi cần lưu. 3. Một số trường hợp đặc biệt có thể còn có thêm phần phụ trợ như bộ đệm (buffer) hoặc metadata khác để tối ưu hóa hiệu suất khi xử lý chuỗi. Việc sắp xếp hợp lý các thành phần này trong cùng một khối bộ nhớ không chỉ giúp tối ưu hóa việc quản lý bộ nhớ mà còn tăng cường tốc độ truy xuất dữ liệu.

  • Kết cấu robj 16 byte.
  • Tiêu đề sdshdr8 3 byte.
  • Mảng sds ký tự tối đa 44 byte.
  • 1 ký tự NULL kết thúc.

Tổng cộng không vượt quá 64 byte (16 + 3 + 44 + 1)tỷ lệ kèo bóng đá trực tiếp, do đó chuỗi ngắn này có thể được phân bổ hoàn toàn trong một khối bộ nhớ có kích thước 64 byte. Điều này giúp tối ưu hóa việc quản lý tài nguyên và tăng hiệu suất xử lý dữ liệu.

Quy trình giải mã của string robj

Khi chúng ta cần lấy giá trị của một chuỗikeo 88, chẳng hạn như khi thực hiện lệnh "get", chúng ta phải thực hiện các bước ngược lại với quá trình mã hóa đã được đề cập trước đó — đó là giải mã. Trong quá trình này, thay vì chuyển đổi thông tin thành các mã hóa phức tạp, chúng ta sẽ sử dụng các thuật toán hoặc phương pháp chuyên biệt để khôi phục dữ liệu ban đầu từ định dạng đã được mã hóa. Điều này đòi hỏi sự cẩn thận và chính xác cao, vì bất kỳ sai sót nào trong quá trình giải mã cũng có thể dẫn đến mất mát hoặc biến dạng dữ liệu. Do đó, việc hiểu rõ cách mã hóa và giải mã hoạt động là vô cùng quan trọng trong việc đảm bảo tính toàn vẹn và an toàn của thông tin.

Tâm điểm của quy trình giải mã này chính là hàm getDecodedObject được tìm thấy trong tậ c. Hàm này đóng vai trò quan trọngkeo 88, là nơi thực hiện các thao tác cốt lõi để giải mã đối tượng cần thiết, đảm bảo tính toàn vẹn và chính xác trong suốt quá trình xử lý.

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
													
														robj
													 *
													getDecodedObject
													(
													robj
													 *
													o
													)
													 {
													
    robj
													 *
													dec
													;
													

    if
													 (
													sdsEncodedObject
													(
													o
													))
													 {
													
        incrRefCount
													(
													o
													);
													
        return
													 o
													;
													
    }
													
    if
													 (
													o
													->
													type
													 ==
													 OBJ_STRING
													 &&
													 o
													->
													encoding
													 ==
													 OBJ_ENCODING_INT
													)
													 {
													
        char
													 buf
													[
													32
													];
													

        ll2string
													(
													buf
													,
													32
													,(
													long
													)
													o
													->
													ptr
													);
													
        dec
													 =
													 createStringObject
													(
													buf
													,
													strlen
													(
													buf
													));
													
        return
													 dec
													;
													
    }
													 else
													 {
													
        serverPanic
													(
													"Unknown encoding type"
													);
													
    }
													
}
													

Quá trình này khá đơn giảntỷ lệ kèo bóng đá trực tiếp, những điểm cần chú ý có:

  • Các đối tượng robj có mã hóa là OBJ_ENCODING_RAW và OBJ_ENCODING_EMBSTR sẽ được trả về mà không có bất kỳ thay đổi nào99WIN, giữ nguyên giá trị ban đầu. Từ góc nhìn của người sử dụng, hai kiểu mã hóa này không có sự khác biệt rõ rệt, vì cả hai đều sử dụng sds (simple dynamic string) bên trong để lưu trữ dữ liệu một cách hiệu quả. Điều này có nghĩa là, cho dù lựa chọn mã hóa nào được sử dụng, các hoạt động xử lý cơ bản vẫn diễn ra giống nhau, giúp người dùng không cần phải lo lắng quá nhiều về sự khác biệt kỹ thuật giữa chúng.
  • Một chuỗi ký tự được mã hóa dưới dạng số với đối tượng robjtỷ lệ kèo bóng đá trực tiếp, sẽ chuyển đổi giá trị long trở lại thành định dạng chuỗi thập phân, sau đó gọi hàm createStringObject để chuyển đổi thành biểu diễn sds. Hãy lưu ý rằng: khi chuyển đổi từ long sang chuỗi sds, độ dài của chuỗi chắc chắn không vượt quá 20 ký tự, và theo cách triển khai của createStringObject, chúng chắc chắn sẽ được mã hóa dưới dạng đối tượng OBJ_ENCODING_EMBSTR. Dưới đây là mã nguồn của hàm createStringObject: ```c robj *createStringObject(char *ptr, size_t len) { robj *o = zmalloc(sizeof(robj) + sizeof(sds)); o->type = OBJ_STRING; o->encoding = OBJ_ENCODING_EMBSTR; o->length = len; o->ptr = ptr; return o; } ```
							
								
1
2
3
4
5
6
													
														robj
													 *
													createStringObject
													(
													const
													 char
													 *
													ptr
													,
													 size_t
													 len
													)
													 {
													
    if
													 (
													len
													 <=
													 OBJ_ENCODING_EMBSTR_SIZE_LIMIT
													)
													
        return
													 createEmbeddedStringObject
													(
													ptr
													,
													len
													);
													
    else
													
        return
													 createRawStringObject
													(
													ptr
													,
													len
													);
													
}
													

Nói lại về mối quan hệ giữa sds và string

Trong bài viết trướctỷ lệ kèo bóng đá trực tiếp, chúng ta đã sơ lược về mối liên hệ giữa sds và string; sau khi bài viết này trình bày khái niệm về robj, chúng ta sẽ cùng tổng hợp lại mối quan hệ giữa sds và string một cách toàn diện hơn. Sds (Simple Dynamic String) là một cấu trúc dữ liệu linh hoạt được Redis sử dụng để quản lý chuỗi. Trong khi đó, string trong Redis là một trong những kiểu dữ liệu cơ bản nhất mà người dùng có thể làm việc. Khi kết hợp hai khái niệm này, sds đóng vai trò như một lớp nền tảng hỗ trợ cho các thao tác vớ Khi Redis lưu trữ một giá trị string, nó thực chất là một đối tượng robj (Redis Object), trong đó có thể chứa nhiều loại dữ liệu khác nhau, bao gồm cả sds. Điều này có nghĩa là sds không chỉ đơn thuần là một chuỗi thông thường mà còn là một phần quan trọng của cơ chế quản lý bộ nhớ Như vậy, sds và string không chỉ là hai khái niệm riêng lẻ mà còn có sự tương tác chặt chẽ trong hệ thống Redis. Sds giúp tối ưu hóa hiệu suất bằng cách cung cấp khả năng mở rộng động, trong khi string đại diện cho các giá trị cụ thể mà người dùng cần lưu trữ và xử lý.

  • Chính xác mà nói99WIN, trong Redis, string được biểu diễn bằng một robj.
  • Biến robj dùng để biểu diễn chuỗi có thể được mã hóa thành ba định dạng bên trong khác nhau: OBJ_ENCODING_RAWtỷ lệ kèo bóng đá trực tiếp, OBJ_ENCODING_EMBSTR và OBJ_ENCODING_INT. Trong đó, hai định dạng mã hóa đầu tiên sử dụng loại dữ liệu sds (safe string data structure) để lưu trữ thông tin, còn định dạng mã hóa cuối cùng, OBJ_ENCODING_INT, sẽ trực tiếp lưu chuỗi dưới dạng kiểu số nguyên long. Điều này cho phép tối ưu hóa hiệu suất lưu trữ và xử lý dữ liệu tùy thuộc vào kích thước và loại giá trị của chuỗi.
  • Khi thực hiện các thao tác như incr (tăng giá trị) hoặc decr (giảm giá trị) trên chuỗi stringtỷ lệ kèo bóng đá trực tiếp, nếu bên trong nó được mã hóa bằng OBJ_ENCODING_INT, Redis có thể thực hiện phép tính cộng trừ trực tiếp mà không cần bước chuyển đổi nào. Tuy nhiên, nếu chuỗi được mã hóa bằng OBJ_ENCODING_RAW hoặc OBJ_ENCODING_EMBSTR, Redis sẽ cố gắng chuyển đổi dữ liệu chuỗi lưu trữ trong sds sang kiểu long trước khi tiến hành phép tính. Nếu quá trình chuyển đổi thành công, Redis sẽ tiếp tục thực hiện các phép toán này; nếu không, nó sẽ không thực hiện gì và có thể trả về lỗi hoặc giá trị mặc định. Điều này cho thấy cách tối ưu hóa của Redis trong việc xử lý các kiểu dữ liệu khác nhau, giúp tăng hiệu suất và giảm thiểu thời gian xử lý trong các trường hợp cụ thể.
  • Khi thực hiện các lệnh như appendkeo 88, setbit và getrange đối với một chuỗi được biểu diễn dưới dạng long (số nguyên lớn), những thao tác này vẫn đang xử lý giá trị của chuỗi ở dạng chuỗi thập phân (chuỗi ký tự), chứ không phải trực tiếp trên giá trị long bên trong. Ví dụ, chuỗi "32", nếu được xem như một mảng ký tự, sẽ chứa hai ký tự có mã ASCII lần lượt là 0x33 và 0x32. Khi chúng ta thực hiện lệnh setbit key 7 0, điều đó tương đương với việc thay đổi ký tự 0x33 thành 0x32, khiến giá trị chuỗi trở thành "22". Tuy nhiên, nếu chuỗi "32" được giải thích theo kiểu số nguyên lớn 64-bit (long), nó sẽ có giá trị 0x0000000000000020. Nếu bạn tiến hành thực hiện các thao tác setbit trên giá trị này, kết quả sẽ không chính xác vì cách biểu diễn đã khác biệt. Do đó, khi triển khai các lệnh này, hệ thống sẽ luôn chuyển đổi giá trị long sang dạng chuỗi trước khi thực hiện các hoạt động cần thiết. Vì lý do giới hạn về mặt trình bày, đoạn mã cụ thể cho việc triển khai ba lệnh này sẽ không được giải thích chi tiết ở đây; những ai quan tâm có thể tham khảo mã nguồn Redis để hiểu rõ hơn.
    • Hàm appendCommand trong t_string.c;
    • Hàm setbitCommand trong biops.c;
    • Hàm getrangeCommand trong t_string.c.

Đáng chú ý là trong việc triển khai các lệnh append và setbittỷ lệ kèo bóng đá trực tiếp, cả hai cuối cùng đều sẽ gọi đến hàm dbUnshareStringValue trong tệp db.c. Hàm này chuyển đổi mã hóa bên trong của đối tượng chuỗi thành OBJ_ENCODING_RAW (chỉ có đối tượng robj với mã hóa này mới cho phép thêm nội dung mới vào sds phía sau một cách tự do) và giải phóng trạng thái chia sẻ đối tượng nếu nó tồn tại. Quá trình này cũng sử dụng hàm getDecodedObject đã được đề cập trước đó.

							
								
1
2
3
4
5
6
7
8
9
10
													
														robj
													 *
													dbUnshareStringValue
													(
													redisDb
													 *
													db
													,
													 robj
													 *
													key
													,
													 robj
													 *
													o
													)
													 {
													
    serverAssert
													(
													o
													->
													type
													 ==
													 OBJ_STRING
													);
													
    if
													 (
													o
													->
													refcount
													 !=
													 1
													 ||
													 o
													->
													encoding
													 !=
													 OBJ_ENCODING_RAW
													)
													 {
													
        robj
													 *
													decoded
													 =
													 getDecodedObject
													(
													o
													);
													
        o
													 =
													 createRawStringObject
													(
													decoded
													->
													ptr
													,
													 sdslen
													(
													decoded
													->
													ptr
													));
													
        decrRefCount
													(
													decoded
													);
													
        dbOverwrite
													(
													db
													,
													key
													,
													o
													);
													
    }
													
    return
													 o
													;
													
}
													

Thao tác đếm tham chiếu của robj

Các hoạt động tăng và giảm số lượng tham chiếu của robj được định nghĩ c:

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
													
														void
													 incrRefCount
													(
													robj
													 *
													o
													)
													 {
													
    o
													->
													refcount
													++
													;
													
}
													

void
													 decrRefCount
													(
													robj
													 *
													o
													)
													 {
													
    if
													 (
													o
													->
													refcount
													 <=
													 0
													)
													 serverPanic
													(
													"decrRefCount against refcount <= 0"
													);
													
    if
													 (
													o
													->
													refcount
													 ==
													 1
													)
													 {
													
        switch
													(
													o
													->
													type
													)
													 {
													
        case
													 OBJ_STRING
													:
													 freeStringObject
													(
													o
													);
													 break
													;
													
        case
													 OBJ_LIST
													:
													 freeListObject
													(
													o
													);
													 break
													;
													
        case
													 OBJ_SET
													:
													 freeSetObject
													(
													o
													);
													 break
													;
													
        case
													 OBJ_ZSET
													:
													 freeZsetObject
													(
													o
													);
													 break
													;
													
        case
													 OBJ_HASH
													:
													 freeHashObject
													(
													o
													);
													 break
													;
													
        default:
													 serverPanic
													(
													"Unknown object type"
													);
													 break
													;
													
        }
													
        zfree
													(
													o
													);
													
    }
													 else
													 {
													
        o
													->
													refcount
													--
													;
													
    }
													
}
													

Chúng ta hãy tập trung vào việc giảm giá trị tham chiếu xuống 1tỷ lệ kèo bóng đá trực tiếp, được gọi là hà Nếu như chỉ còn một tham chiếu cuối cùng (giá trị refcount hiện tại là 1) và sau khi hàm decrRefCount được thực thi, toàn bộ đối tượng robj sẽ bị giải phóng hoàn toàn. Điều này xảy ra bởi vì khi giá trị tham chiếu giảm xuống 0, không còn đối tượng nào giữ liên kết với nó nữa, dẫn đến việc giải phóng tài nguyên trong bộ nhớ tự động.

Khi sử dụng lệnh del trong Redistỷ lệ kèo bóng đá trực tiếp, nó sẽ phụ thuộc vào hoạt động decrRefCount để giải phóng giá trị value. Thêm vào đó, quy trình này đóng vai trò quan trọng trong việc quản lý bộ nhớ của Redis, đảm bảo rằng tài nguyên không còn được sử dụng sẽ được dọn dẹp một cách hiệu quả và tối ưu hóa hiệu suất hệ thống.


Dựa trên nội dung bài viết mà chúng ta vừa thảo luậntỷ lệ kèo bóng đá trực tiếp, có thể dễ dàng nhận ra rằng robj chính là đại diện cho cấu trúc dữ liệu đầu tiên mà Redis cung cấp ra bên ngoài: string, list, hash, set và Mỗi loại cấu trúc dữ liệu này, ở tầng thứ hai, sẽ được thực hiện bởi một hoặc nhiều cấu trúc dữ liệu cụ thể như dict, sds, ziplist, quicklist, skiplist,... Việc xác định loại cấu trúc nào sẽ được sử dụng dưới lớp thứ hai để hỗ trợ các loại dữ liệu ở lớp trên sẽ phụ thuộc vào encoding khác nhau. Có thể nói, robj đóng vai trò như một cây cầu kết nối giữa hai tầng cấu trúc dữ liệu này, giúp tối ưu hóa hiệu suất và linh hoạt trong việc quản lý bộ nhớ của Redis.

Bài viết này sẽ đi sâu vào việc trình bày chi tiết về thực hiện cơ bản của đối tượng chuỗi kiểu OBJ_STRING trong Redis99WIN, quá trình mã hóa và giải mã của nó đóng vai trò quan trọng và được áp dụng rộng rãi. Chúng ta có thể gặp lại các khái niệm này trong những phần thảo luận tiếp theo. Hiện tại, khi đã nắm vững nền tảng lý thuyết về robj, bài viết tiếp theo của chúng ta sẽ tập trung vào ziplist và mối liên hệ của nó vớ


Phụ lục mã hóa chuỗi thành kiểu long commit f648c5a


Bài viết gốckeo 88, vui lòng ghi rõ nguồn và bao gồm mã QR bên dưới! Nếu không, từ chối tái bản!
Liên kết bài viết: /w8pksnr4.html
Hãy theo dõi tài khoản Weibo cá nhân của tôi: Tìm kiếm tên "Trương Tiết Lệ" trên Weibo.
Tài khoản WeChat của tôi: tielei-blog (Trương Tiết Lệ)
Bài trước: Phân tích sâu cấu trúc dữ liệu nội bộ của Redis (2) —— sds
Bài sau: Con gái và những cuốn truyện tranh của nó

Bài viết mới nhất