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 (1) —— dict


Chi tiết về Kết cấu Dữ liệu Bên trong Redis

cấu trúc dữ liệu

Khía cạnh đầu tiên99win club, từ góc độ người sử dụng. Ví dụ:

  • string
  • list
  • hash
  • set
  • sorted set

Khía cạnh này cũng là giao diện gọi API mà Redis cung cấp ra bên ngoài.

Khía cạnh thứ haiđá gà trực tiếp app, từ góc độ triển khai nội bộ, thuộc về phần thực hiện ở tầng thấp hơn. Ví dụ:

  • dict
  • sds
  • ziplist
  • quicklist
  • skiplist

Cấu trúc dữ liệu http://redis.io/topics/data-types-intro Bài viết này sẽ tập trung vào việc thảo luận về khía cạnh thứ hai99win club, đó là cách Redis triển khai các cấu trúc dữ liệu bên trong ở cấp độ thứ hai, cũng như mối quan hệ giữa các cấu trúc dữ liệu ở hai cấp độ này: Redis làm thế nào để xây dựng các cấu trúc dữ liệu cao cấp hơn ở cấp độ đầu tiên bằng cách kết hợp các thành phần cơ bản từ cấp độ thứ hai. Ngoài ra, việc hiểu rõ cách hoạt động của các cấu trúc dữ liệu cơ bản tại cấp độ thứ hai sẽ giúp chúng ta có cái nhìn sâu sắc hơn về hiệu suất và khả năng xử lý dữ liệu của Redis. Redis sử dụng một loạt các công cụ như danh sách liên kết đôi, cây cân bằng, hoặc chuỗi phân tán để tạo nên các tính năng phức tạp mà người dùng thường xuyên sử dụng, chẳng hạn như chuỗi (string), danh sách (list), tập hợp (set), hoặc bảng băm (hash). Mỗi loại dữ liệu trong Redis đều được xây dựng dựa trên các yếu tố cơ bản như mảng, cây nhị phân, hoặc chuỗi, và được tùy chỉnh để phù hợp với yêu cầu cụ thể của từng trường hợp sử dụng. Điều này không chỉ giúp tối ưu hóa hiệu quả lưu trữ mà còn đảm bảo rằng Redis có thể xử lý hàng tỷ giao dịch mỗi giây một cách nhanh chóng và ổn định.

Khi thảo luận về việc triển khai nội bộ của bất kỳ hệ thống nàođá gà trực tiếp app, chúng ta cần trước tiên xác định rõ các nguyên tắc thiết kế của nó. Điều này giúp chúng ta hiểu sâu hơn về ý định thực sự đằng sau việc tại sao hệ thống lại được thiết kế theo cách đó. Trong phần tiếp theo của bài viết này, chúng tôi sẽ tập trung vào một số điểm chính sau đây: Trước hết, việc hiểu rõ bản chất của các nguyên tắc thiết kế không chỉ giúp chúng ta đánh giá cao tầm quan trọng của từng yếu tố mà còn cung cấp cái nhìn toàn diện về cách mà hệ thống hoạt động hiệu quả. Thứ hai, khi phân tích kỹ lưỡng những gì đã được thực hiện, chúng ta có thể khám phá ra những điểm mạnh và điểm yếu tiềm ẩn trong cơ chế vận hành của hệ thống. Cuối cùng, chúng tôi sẽ cố gắng đưa ra một số nhận định về cách cải tiến có thể áp dụng nhằm tối ưu hóa hiệu suất tổng thể. Chúng tôi hy vọng rằng qua việc đi sâu vào phân tích này, độc giả sẽ có được cái nhìn mới mẻ và sâu sắc hơn về hệ thống đang được thảo luận.

  • Hiệu quả lưu trữ (memory efficiency) là một yếu tố cực kỳ quan trọng đối với Redisđá gà trực tiếp app, vì đây là công cụ được thiết kế đặc biệt để lưu trữ dữ liệu và tiêu tốn chủ yếu của nó chính là tài nguyên bộ nhớ. Do đó, việc tối ưu hóa và tiết kiệm bộ nhớ đóng vai trò then chốt trong hoạt động của Redis. Điều này cho thấy rằng Redis đã được phát triển một cách cẩn thận, tập trung vào việc nén dữ liệu, giảm thiểu các mảnh vỡ bộ nhớ (memory fragmentation), cũng như tối ưu hóa cách quản lý bộ nhớ để đạt hiệu suất cao nhất có thể. Với khả năng này, Redis không chỉ giúp người dùng tiết kiệm chi phí mà còn tăng cường hiệu quả vận hành hệ thống tổng thể.
  • Thời gian phản hồi nhanh (fast response time) là một trong những yếu tố quan trọng trong việc tối ưu hóa hệ thống. Ngược lại với điều đó là khả năng xử lý dữ liệu lớn (high throughput). Redis được thiết kế để phục vụ các yêu cầu truy cập trực tuyến99WIN, do đó, việc đảm bảo thời gian phản hồi nhanh cho mỗi yêu cầu là ưu tiên hàng đầu, hơn cả khả năng xử lý khối lượng công việc lớn. Tuy nhiên, đôi khi hai mục tiêu này có thể mâu thuẫn nhau, vì tập trung vào thời gian phản hồi nhanh có thể làm giảm hiệu quả tổng hợp của hệ thống trong việc xử lý nhiều yêu cầu cùng lúc. Trong thực tế, tùy thuộc vào yêu cầu cụ thể của từng ứng dụng, người lập trình phải cân nhắc giữa hai yếu tố này để tìm ra giải pháp phù hợp nhất. Nếu ứng dụng cần xử lý hàng loạt giao dịch trong thời gian ngắn, thì khả năng xử lý dữ liệu lớn sẽ đóng vai trò quan trọng. Nhưng nếu mục tiêu chính là tạo ra trải nghiệm mượt mà và tức thời cho người dùng, thì chắc chắn thời gian phản hồi nhanh sẽ là ưu tiên hàng đầu. Redis, với khả năng xử lý nhanh chóng các yêu cầu đơn lẻ, thường được lựa chọn trong các trường hợp cần tối ưu hóa thời gian phản hồi, ngay cả khi nó không phải là giải pháp lý tưởng cho tất cả các bài toán về khả năng mở rộng.
  • Redis hoạt động theo mô hình đơn luồng (single-threaded)99win club, điều này không đồng nghĩa với việc nó bị giới hạn bởi tài nguyên CPU. Ngược lại, điểm nghẽn chính của Redis nằm ở việc truy cập bộ nhớ và xử lý IO mạng. Tuy nhiên, thiết kế dựa trên một luồng duy nhất mang đến lợi ích lớn là giúp tối giản hóa việc triển khai các cấu trúc dữ liệu và thuật toán. Thay vào đó, Redis sử dụng cơ chế IO bất đồng bộ (asynchronous IO) và pipelining để đạt được khả năng truy cập song công hiệu quả. Rõ ràng, việc áp dụng mô hình đơn luồng cũng đặt ra yêu cầu cao hơn đối với thời gian phản hồi nhanh chóng cho từng yêu cầu cụ thể, vì mọi tác vụ đều phụ thuộc vào cùng một luồng xử lý.

Chi tiết về Kết cấu Dữ liệu Nội bộ của Redis

Dict là một cấu trúc dữ liệu được thiết kế để duy trì mối quan hệ ánh xạ giữa key và value99WIN, tương tự như Map hoặc dictionary trong nhiều ngôn ngữ lập trình khác. Trong Redis, tất cả các mối quan hệ ánh xạ từ key đến value trong một database đều được duy trì thông qua một dict. Tuy nhiên, đây chỉ là một trong những ứng dụng của dict trong Redis mà thôi. Có rất nhiều nơi khác mà dict được sử dụng trong hệ thống này. Ví dụ, khi một hash trong Redis có số lượng field lớn, dict sẽ được dùng để lưu trữ chúng. Thêm vào đó, Redis thường kết hợp sử dụng dict cùng với skiplist để quản lý một tập hợp đã sắp xếp (sorted set). Những chi tiết này, chúng ta sẽ tìm hiểu kỹ hơn ở các bài viết sau. Còn trong bài viết hiện tại, chúng ta sẽ tập trung phân tích cách thức triển khai của dict. Trước tiên, cần hiểu rằng dict trong Redis không chỉ đơn thuần là một công cụ để ánh xạ key-value mà còn đóng vai trò quan trọng trong việc tối ưu hóa hiệu suất của hệ thống. Với cơ chế hashing thông minh, dict giúp giảm thiểu thời gian truy xuất dữ liệu và tăng cường khả năng mở rộng cho các yêu cầu phức tạp. Điều này đặc biệt hữu ích khi xử lý các trường hợp như yêu cầu tìm kiếm, thêm hoặc xóa dữ liệu nhanh chó Đặc biệt, dict trong Redis cũng được thiết kế để hỗ trợ khả năng tự động điều chỉnh kích thước bộ nhớ khi cần thiết. Khi số lượng phần tử trong dict vượt quá giới hạn tối đa, nó sẽ tự động tăng kích thước bộ nhớ để đảm bảo hiệu suất hoạt động ổn định. Đây là một tính năng quan trọng giúp Redis duy trì hiệu quả cao trong mọi tình huống vận hành. Như vậy, dù chỉ là một thành phần nhỏ trong hệ thống Redis, dict vẫn giữ vai trò then chốt trong việc tạo nên sự linh hoạt và hiệu quả trong việc quản lý dữ liệu. Chúng ta sẽ tiếp tục khám phá sâu hơn về cách hoạt động chi tiết của dict trong các phần tiếp theo của bài viết.

Dict về cơ bản được thiết kế để giải quyết các vấn đề tìm kiếm (searching) trong lĩnh vực thuật toán. Thông thường99WIN, các phương pháp giải quyết vấn đề tìm kiếm có thể chia thành hai nhóm lớn: một là dựa trên các cây cân bằng (balanced tree), và hai là dựa trên bảng băm (hash table). Các Map hoặc dictionary mà chúng ta sử dụng hàng ngày chủ yếu đều được xây dựng dựa trên bảng băm. Trong trường hợp không yêu cầu dữ liệu phải được sắp xếp theo thứ tự và có thể duy trì xác suất xung đột giá trị băm ở mức thấp, hiệu suất tìm kiếm dựa trên bảng băm có thể đạt được mức độ hiệu quả rất cao, gần như O(1), đồng thời cách thực hiện cũng khá đơn giản và trực quan.

thời gian phản hồi nhanh

Tiếp theo sẽ đưa ra chi tiết giới thiệu.

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

Để thực hiện việc tái phân tán hash theo cách tăng dần (incremental rehashing)đá gà trực tiếp app, cấu trúc dữ liệu của dict bao gồm hai bảng hash. Trong quá trình tái phân tán, dữ liệu sẽ được di chuyển từ bảng hash đầu tiên sang bảng hash thứ hai một cách từ từ và hiệu quả. Bên cạnh đó, việc sử dụng hai bảng hash này không chỉ giúp quá trình tái phân dữ liệu trở nên mượt mà hơn mà còn đảm bảo rằng các hoạt động đọc/ghi vẫn có thể diễn ra bình thường trong khi hệ thống đang xử lý việc phân phối lại dữ liệu. Điều này làm cho dict trở nên linh hoạt và ổn định hơn trong nhiều tình huống thực tế.

Định nghĩa mã nguồn C của dict như sau (trích từ tệp dict.h của mã nguồn Redis):

							
								
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
													
														typedef
													 struct
													 dictEntry
													 {
													
    void
													 *
													key
													;
													
    union
													 {
													
        void
													 *
													val
													;
													
        uint64_t
													 u64
													;
													
        int64_t
													 s64
													;
													
        double
													 d
													;
													
    }
													 v
													;
													
    struct
													 dictEntry
													 *
													next
													;
													
}
													 dictEntry
													;
													

typedef
													 struct
													 dictType
													 {
													
    unsigned
													 int
													 (
													*
													hashFunction
													)(
													const
													 void
													 *
													key
													);
													
    void
													 *
													(
													*
													keyDup
													)(
													void
													 *
													privdata
													,
													 const
													 void
													 *
													key
													);
													
    void
													 *
													(
													*
													valDup
													)(
													void
													 *
													privdata
													,
													 const
													 void
													 *
													obj
													);
													
    int
													 (
													*
													keyCompare
													)(
													void
													 *
													privdata
													,
													 const
													 void
													 *
													key1
													,
													 const
													 void
													 *
													key2
													);
													
    void
													 (
													*
													keyDestructor
													)(
													void
													 *
													privdata
													,
													 void
													 *
													key
													);
													
    void
													 (
													*
													valDestructor
													)(
													void
													 *
													privdata
													,
													 void
													 *
													obj
													);
													
}
													 dictType
													;
													

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing99WIN, for the old to the new table. */
typedef
													 struct
													 dictht
													 {
													
    dictEntry
													 **
													table
													;
													
    unsigned
													 long
													 size
													;
													
    unsigned
													 long
													 sizemask
													;
													
    unsigned
													 long
													 used
													;
													
}
													 dictht
													;
													

typedef
													 struct
													 dict
													 {
													
    dictType
													 *
													type
													;
													
    void
													 *
													privdata
													;
													
    dictht
													 ht
													[
													2
													];
													
    long
													 rehashidx
													;
													 /* rehashing not in progress if rehashidx == -1 */
													
    int
													 iterators
													;
													 /* number of iterators currently running */
													
}
													 dict
													;
													

Để có thể hiểu rõ hơn về định nghĩa cấu trúc dữ liệu của dictđá gà trực tiếp app, chúng ta sẽ biểu diễn nó bằng một sơ đồ cấu trúc. Dưới đây là hình ảnh.

Sơ đồ cấu trúc dict của Redis

Dựa trên mã nguồn và sơ đồ cấu trúc được cung cấp ở trênđá gà trực tiếp app, ta có thể dễ dàng nhận ra cấu trúc của từ điển (dict). Một dict bao gồm các thành phần sau đây: Trước hết, mỗi dict luôn bắt đầu bằng dấu ngoặc nhọn `{}` để xác định phạm vi của nó. Tiếp theo, mỗi thành phần bên trong dict sẽ là một cặp "key-value", tức là một cặp giá trị duy nhất được phân tách bởi dấu hai chấm `:`. Các cặp này sẽ được phân cách nhau bằng dấu phẩy `,`, tạo nên sự mạch lạc và rõ ràng trong cấu trúc. Ví dụ, một dict đơn giản có thể trông như thế này: ``` { "name": "John", "age": 30, "is_student": False } ``` Trong đó: - `"name"` là key và `"John"` là value tương ứng. - `"age"` là key và `30` là value. - `"is_student"` là key và `False` (giá trị logic) là value. Thêm vào đó, dict hỗ trợ nhiều loại dữ liệu khác nhau làm giá trị, bao gồm chuỗi, số, boolean, danh sách (list), hoặc thậm chí là các dict lồng nhau. Điều này khiến cho dict trở thành công cụ mạnh mẽ và linh hoạt trong việc lưu trữ và truy xuất thông tin.

  • Một con trỏ hướng đến cấu trúc dictType (loại dữ liệu). Nó cho phép lưu trữ bất kỳ loại dữ liệu nào trong key và value của dict bằng cách sử dụng phương pháp tùy chỉnh. Thay vì bị giới hạn bởi các kiểu dữ liệu cố địnhđá gà trực tiếp app, cách tiếp cận này mang lại sự linh hoạt cao trong việc quản lý và thao tác dữ liệu trong từ điển.
  • Một con trỏ dữ liệu riêng (privdata). Được truyền vào bởi người gọi khi tạo dict.
  • Bạn có hai bảng băm (ht[2]). Chỉ trong quá trình tái phân tán dữ liệuđá gà trực tiếp app, cả ht[0] và ht[1] mới cùng lúc hoạt động. Còn thông thường, chỉ có ht[0] là có hiệu lực, còn ht[1] sẽ không chứa bất kỳ dữ liệu nào. Hình ảnh trên minh họa tình huống khi quá trình tái phân tán đang diễn ra ở một bước cụ thể giữa chừng.
  • Trạng thái của chỉ mục rehash hiện tại được xác định bởi biế Nếu rehashidx bằng -199win club, điều đó có nghĩa là hệ thống không đang trong quá trì Tuy nhiên, nếu giá trị khác -1, điều đó cho thấy quá trình rehashing đang diễn ra và giá trị này sẽ biểu thị bước mà quá trình rehashing đã đạt đến. Quá trình này đóng vai trò quan trọng để duy trì hiệu quả hoạt động khi số lượng dữ liệu tăng lên.
  • Số lượng iterator đang được duyệt hiện tại. Đây không phải là trọng tâm mà chúng ta đang thảo luận99win club, hãy tạm thời bỏ qua.

Trong cấu trúc dictType99WIN, có chứa một số con trỏ đến hàm, cho phép người gọi của dict tùy chỉnh các thao tác liên quan đến key và value. Các thao tác này bao gồm: - Tạo và quản lý việc thêm hoặc xóa key-value. - So sánh hai key để xác định thứ tự hoặc tính chất bằng nhau. - Hash hóa key để tạo mã định danh duy nhất. - Sắp xếp và tìm kiếm key trong trường hợp cần thiết. - Xử lý các lỗi hoặc tình huống bất thường khi thực hiện thao tác trên dict. Tính linh hoạt này giúp dictType trở nên mạnh mẽ và phù hợp với nhiều ứng dụng khác nhau trong hệ thống.

  • hashFunction99win club, thuật toán băm để tính giá trị băm của key.
  • Hàm keyDup và valDup được định nghĩa để tạo ra các hàm sao chép riêng biệt cho key và value. Khi cần thiết99WIN, chúng sẽ thực hiện việc sao chép sâu (deep copy) thay vì chỉ truyền tiếp các con trỏ đối tượng. Điều này giúp đảm bảo rằng dữ liệu gốc không bị ảnh hưởng trong quá trình xử lý và duy trì tính độc lập giữa các bản sao.
  • keyCompaređá gà trực tiếp app, xác định hoạt động so sánh giữa hai key, được sử dụng khi tìm kiế
  • Bạn có thể định nghĩa các hàm huỷ `keyDestructor` và `valDestructor` để xử lý việc giải phóng tài nguyên hoặc thực hiện bất kỳ tác vụ dọn dẹp nào liên quan đến key và value. Điều này đặc biệt hữu ích khi bạn cần đảm bảo rằng các đối tượng được quản lý một cách hiệu quả và an toàn trong suốt vòng đời của chúng.

Con trỏ dữ liệu riêng tư (privdata) là một tham chiếu được truyền lại cho người gọi khi một số chức năng của dictType được thực hiện. Trong quá trình xử lýđá gà trực tiếp app, con trỏ này đóng vai trò quan trọng trong việc lưu trữ hoặc quản lý các thông tin đặc thù mà mỗi lần gọi có thể cần đến, từ đó giúp tăng tính linh hoạt và hiệu quả cho hoạt động của hệ thống.

Cần xem xét kỹ hơn cấu trúc dictht. Nó định nghĩa cấu trúc của bảng băm99WIN, bao gồm các thành phần sau:

  • Một mảng con trỏ dictEntry (gọi là bảng băm - table). Giá trị băm của key cuối cùng sẽ ánh xạ đến một vị trí cụ thể trong mảng này (tức là một bucket). Nếu nhiều key được ánh xạ đến cùng một vị trí99WIN, xung đột sẽ xảy ra, và khi đó, một danh sách liên kết (linked list) của các dictEntry sẽ được tạo ra tại vị trí đó.
  • size: chỉ số độ dài của mảng các con trỏ Nó luôn là lũy thừa của 2.
  • Tham số sizemask được sử dụng để ánh xạ giá trị băm (hash value) đến vị trí chỉ mục trong bảng (table). Giá trị của sizemask bằng (size - 1)đá gà trực tiếp app, chẳng hạn như 7, 15, 31, 63 và các giá trị tương tự, tức là các số có biểu diễn nhị phân toàn bit 1. Mỗi key trước tiên sẽ được tính toán qua hàm băm (hashFunction) để tạo ra một giá trị băm, sau đó thực hiện phép tính (giá trị băm & sizemask) để xác định vị trí tương ứng trong bảng. Điều này về cơ bản giống với việc tính toán dư (remainder), cụ thể là (giá trị băm % size). Thêm vào đó, việc chọn sizemask theo cách này giúp tối ưu hóa hiệu suất vì phép AND giữa giá trị băm và sizemask thường nhanh hơn so với phép chia trong nhiều hệ thống máy tính. Điều này làm cho quy trình ánh xạ trở nên mượt mà và hiệu quả hơn khi xử lý lượng lớn dữ liệu.
  • Trong ngữ cảnh này99WIN, thuộc tính "used" được sử dụng để đếm số lượng mục hiện có trong cấu trúc dữ liệu dict. Tỷ lệ giữa giá trị của used và size sẽ xác định hệ số tải (load factor). Khi tỷ lệ này càng cao, xác suất xảy ra xung đột giá trị băm (hash collision) cũng sẽ tăng lên đáng kể, làm ảnh hưởng đến hiệu quả hoạt động củ

Trong cấu trúc dictEntry99WIN, bao gồm các thành phần k, v và con trỏ next để trỏ đến phần tử tiếp theo trong danh sách liên kết. Biến k là một con trỏ void, điều này cho phép nó có thể trỏ đến bất kỳ kiểu dữ liệu nào. Còn v là một union (liên hợp), khi giá trị của nó là uint64_t, int64_t hoặc double thì không cần thêm bộ nhớ lưu trữ bổ sung, điều này giúp giảm thiểu mảnh vụn bộ nhớ. Tất nhiên, v cũng có thể là một con trỏ void để lưu trữ bất kỳ loại dữ liệu nào. Cấu trúc dictEntry không chỉ đơn giản là một công cụ lưu trữ mà còn mang tính linh hoạt cao. Với khả năng tối ưu hóa bộ nhớ thông qua union, nó đảm bảo hiệu suất tốt hơn trong việc quản lý tài nguyên hệ thống. Đồng thời, việc sử dụng con trỏ void làm tham số k và v cho phép nó tương thích với hầu hết các loại dữ liệu hiện có, từ đó mở rộng phạm vi ứng dụng của dictEntry trong các hệ thống phức tạp.

Tạo dict (dictCreate)

							
								
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
													
														dict
													 *
													dictCreate
													(
													dictType
													 *
													type
													,
													
        void
													 *
													privDataPtr
													)
													
{
													
    dict
													 *
													d
													 =
													 zmalloc
													(
													sizeof
													(
													*
													d
													));
													

    _dictInit
													(
													d
													,
													type
													,
													privDataPtr
													);
													
    return
													 d
													;
													
}
													

int
													 _dictInit
													(
													dict
													 *
													d
													,
													 dictType
													 *
													type
													,
													
        void
													 *
													privDataPtr
													)
													
{
													
    _dictReset
													(
													&
													d
													->
													ht
													[
													0
													]);
													
    _dictReset
													(
													&
													d
													->
													ht
													[
													1
													]);
													
    d
													->
													type
													 =
													 type
													;
													
    d
													->
													privdata
													 =
													 privDataPtr
													;
													
    d
													->
													rehashidx
													 =
													 -
													1
													;
													
    d
													->
													iterators
													 =
													 0
													;
													
    return
													 DICT_OK
													;
													
}
													

static
													 void
													 _dictReset
													(
													dictht
													 *
													ht
													)
													
{
													
    ht
													->
													table
													 =
													 NULL
													;
													
    ht
													->
													size
													 =
													 0
													;
													
    ht
													->
													sizemask
													 =
													 0
													;
													
    ht
													->
													used
													 =
													 0
													;
													
}
													

Hàm dictCreate sẽ cấp phát bộ nhớ cho cấu trúc dữ liệu dict và gán giá trị ban đầu cho các biến liên quan. Trong đó99WIN, hai bảng băm ht[0] và ht[1] ban đầu chưa được cấp phát không gian, cả hai con trỏ table đều được đặt thành NULL. Điều này có nghĩa là việc phân bổ thực tế chỉ xảy ra khi dữ liệu đầu tiên được chèn vào. Khi đó, hệ thống mới bắt đầu thiết lập và quản lý không gian lưu trữ theo yêu cầu của các phần tử sắp tới.

Tìm kiếm dict (dictFind)

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
													
														#define dictIsRehashing(d) ((d)->rehashidx != -1)

													
dictEntry
													 *
													dictFind
													(
													dict
													 *
													d
													,
													 const
													 void
													 *
													key
													)
													
{
													
    dictEntry
													 *
													he
													;
													
    unsigned
													 int
													 h
													,
													 idx
													,
													 table
													;
													

    if
													 (
													d
													->
													ht
													[
													0
													].
													used
													 +
													 d
													->
													ht
													[
													1
													].
													used
													 ==
													 0
													)
													 return
													 NULL
													;
													 /* dict is empty */
													
    if
													 (
													dictIsRehashing
													(
													d
													))
													 _dictRehashStep
													(
													d
													);
													
    h
													 =
													 dictHashKey
													(
													d
													,
													 key
													);
													
    for
													 (
													table
													 =
													 0
													;
													 table
													 <=
													 1
													;
													 table
													++
													)
													 {
													
        idx
													 =
													 h
													 &
													 d
													->
													ht
													[
													table
													].
													sizemask
													;
													
        he
													 =
													 d
													->
													ht
													[
													table
													].
													table
													[
													idx
													];
													
        while
													(
													he
													)
													 {
													
            if
													 (
													key
													==
													he
													->
													key
													 ||
													 dictCompareKeys
													(
													d
													,
													 key
													,
													 he
													->
													key
													))
													
                return
													 he
													;
													
            he
													 =
													 he
													->
													next
													;
													
        }
													
        if
													 (
													!
													dictIsRehashing
													(
													d
													))
													 return
													 NULL
													;
													
    }
													
    return
													 NULL
													;
													
}
													

Mã nguồn của dictFind trên đây99WIN, dựa trên việc dict hiện tại có đang tiến hành băm lại hay không, lần lượt thực hiện các bước sau:

  • Nếu quá trình tái phân tán hash đang diễn rađá gà trực tiếp app, hãy tiến hành đẩy nó tiếp thêm một bước (cụ thể là gọi hàm _dictRehashStep). Thực tế, ngoài việc tìm kiếm, các thao tác như chèn và xóa cũng có khả năng kích hoạt hành động này. Điều này giúp phân tán quá trình tái phân tán hash qua nhiều thao tác tìm kiếm, chèn và xóa thay vì tập trung tất cả vào một thao tác duy nhất để hoàn thành toàn bộ. Chính sự phân tán này giúp tăng hiệu quả và giảm tải cho hệ thống trong từng bước xử lý nhỏ hơn và linh hoạt hơn.
  • Bạn có thể tính toán giá trị băm của key (gọi hàm dictHashKey99win club, trong đó sẽ gọi đến hàm hashFunction đã được đề cập trước đó). Hàm này sẽ thực hiện việc xử lý và chuyển đổi key thành một giá trị băm duy nhất dựa trên thuật toán đã được định nghĩ
  • Trước tiên99WIN, bạn cần kiểm tra trong bảng băm đầu tiên, cụ thể là tại vị trí ht[0]. Để xác định vị trí tương ứng trong mảng table, hãy sử dụng giá trị băm (như đã giải thích trước đó, bằng cách thực hiện phép AND giữa giá trị băm và sizemask), sau đó tiến hành tìm kiếm trong danh sách liên kết dictEntry tại vị trí đó. Trong quá trình tìm kiếm, cần so sánh key; ở đây sẽ gọi hàm dictCompareKeys, hàm này sẽ tiếp tục gọi đến hàm keyCompare đã đề cập trước đó để thực hiện so sánh. Nếu tìm thấy mục cần tìm, nó sẽ trả về giá trị đó. Nếu không, bạn sẽ chuyển sang bước tiếp theo.
  • Bạn có thể xác định xem hiện tại có đang ở giai đoạn tái phân tán hay không. Nếu không99WIN, kết quả tìm kiếm trên bảng băm ht[0] sẽ là kết quả cuối cùng (nếu không tìm thấy, trả về NULL). Ngược lại, nếu đang trong quá trình tái phân tán, hãy tiến hành tìm kiếm trên bảng băm ht[1] (thực hiện theo cách tương tự như bước trước đó).

Chúng ta cần xem xét kỹ hơn cách thực hiện của _dictRehashStep99WIN, tiến trình băm lại từng bước.

							
								
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
													
														static
													 void
													 _dictRehashStep
													(
													dict
													 *
													d
													)
													 {
													
    if
													 (
													d
													->
													iterators
													 ==
													 0
													)
													 dictRehash
													(
													d
													,
													1
													);
													
}
													

int
													 dictRehash
													(
													dict
													 *
													d
													,
													 int
													 n
													)
													 {
													
    int
													 empty_visits
													 =
													 n
													*
													10
													;
													 /* Max number of empty buckets to visit. */
													
    if
													 (
													!
													dictIsRehashing
													(
													d
													))
													 return
													 0
													;
													

    while
													(
													n
													--
													 &&
													 d
													->
													ht
													[
													0
													].
													used
													 !=
													 0
													)
													 {
													
        dictEntry
													 *
													de
													,
													 *
													nextde
													;
													

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
													
        assert
													(
													d
													->
													ht
													[
													0
													].
													size
													 >
													 (
													unsigned
													 long
													)
													d
													->
													rehashidx
													);
													
        while
													(
													d
													->
													ht
													[
													0
													].
													table
													[
													d
													->
													rehashidx
													]
													 ==
													 NULL
													)
													 {
													
            d
													->
													rehashidx
													++
													;
													
            if
													 (
													--
													empty_visits
													 ==
													 0
													)
													 return
													 1
													;
													
        }
													
        de
													 =
													 d
													->
													ht
													[
													0
													].
													table
													[
													d
													->
													rehashidx
													];
													
        /* Move all the keys in this bucket from the old to the new hash HT */
													
        while
													(
													de
													)
													 {
													
            unsigned
													 int
													 h
													;
													

            nextde
													 =
													 de
													->
													next
													;
													
            /* Get the index in the new hash table */
													
            h
													 =
													 dictHashKey
													(
													d
													,
													 de
													->
													key
													)
													 &
													 d
													->
													ht
													[
													1
													].
													sizemask
													;
													
            de
													->
													next
													 =
													 d
													->
													ht
													[
													1
													].
													table
													[
													h
													];
													
            d
													->
													ht
													[
													1
													].
													table
													[
													h
													]
													 =
													 de
													;
													
            d
													->
													ht
													[
													0
													].
													used
													--
													;
													
            d
													->
													ht
													[
													1
													].
													used
													++
													;
													
            de
													 =
													 nextde
													;
													
        }
													
        d
													->
													ht
													[
													0
													].
													table
													[
													d
													->
													rehashidx
													]
													 =
													 NULL
													;
													
        d
													->
													rehashidx
													++
													;
													
    }
													

    /* Check if we already rehashed the whole table... */
													
    if
													 (
													d
													->
													ht
													[
													0
													].
													used
													 ==
													 0
													)
													 {
													
        zfree
													(
													d
													->
													ht
													[
													0
													].
													table
													);
													
        d
													->
													ht
													[
													0
													]
													 =
													 d
													->
													ht
													[
													1
													];
													
        _dictReset
													(
													&
													d
													->
													ht
													[
													1
													]);
													
        d
													->
													rehashidx
													 =
													 -
													1
													;
													
        return
													 0
													;
													
    }
													

    /* More to rehash... */
													
    return
													 1
													;
													
}
													

Trong mỗi lần thực hiện dictRehash99WIN, ít nhất n bước của quá trình tái phân tán sẽ được tiến hành (trừ khi toàn bộ quá trình tái phân phối kết thúc trước khi đạt đến n bước). Tại mỗi bước, một bucket cụ thể từ danh sách dictEntry nằm trong mảng ht[0] sẽ được di chuyển sang mảng ht[1]. Vị trí mới của các dictEntry này trên mảng ht[1] sẽ được tính toán lại dựa trên giá trị sizemask của mảng ht[1]. Biến rehashidx giữ track của vị trí bucket chưa được di chuyển (còn đang chờ xử lý) trong mảng ht[0].

Khi dictRehash được thực hiện và bucket mà rehashidx đang chỉ tới hoàn toàn trống (không có dictEntry nào)99WIN, điều đó có nghĩa là không có dữ liệu nào cần được di chuyển ở thời điểm này. Tại thời điểm này, hệ thống sẽ cố gắng tìm kiếm trong mảng ht[0].table để tìm vị trí tiếp theo chứa dữ liệu. Nó sẽ lần lượt kiểm tra các bucket tiếp theo cho đến khi tìm thấy một bucket có dữ liệu. Tuy nhiên, nếu nó không thể tìm thấy bất kỳ bucket nào chứa dữ liệu sau khi thực hiện tối đa n * 10 bước, quá trình tái phân tán hash (rehash) tạm thời sẽ bị dừng lại để tránh lãng phí tài nguyên.

Cuối cùng99win club, khi tất cả dữ liệu trên ht[0] đã được di chuyển hoàn toàn sang ht[1] (tức là d->ht[0].used == 0), quá trình tái phân tán hash sẽ kết thúc. Lúc này, ht[0] sẽ nhận toàn bộ nội dung từ ht[1], trong khi ht[1] sẽ được làm trống lại về trạng thái ban đầu, sẵn sàng cho các hoạt động tiếp theo.

Dựa trên phân tích về quá trình tái-hashing được đề cập ở trên99WIN, chúng ta có thể dễ dàng nhận ra rằng hình ảnh cấu trúc dict được trình bày trong phần trước của bài viết chính là tình huống khi rehashidx = 2. Hai bucket đầu tiên (ht[0].table[0] và ht[0].table[1]) đã được di chuyển hoàn toàn sang ht[1]. Bên cạnh đó, có thể thấy rằng việc tái-hashing đang diễn ra một cách có hệ thống, đảm bảo rằng các giá trị trong hash table cũ (ht[0]) được sắp xếp lại một cách hợp lý vào bảng mới (ht[1]). Điều này cho phép duy trì hiệu quả hoạt động của thuật toán trong suốt quá trình xử lý dữ liệu lớn hoặc khi cần mở rộng kích thước củ

Chèn dict (dictAdd và dictReplace)

dictAdd chèn cặp mới key và value99WIN, nếu key đã tồn tại thì sẽ thất bại.

Hàm dictReplace cũng thêm vào một cặp khóa và giá trị99win club, nhưng khi khóa đã tồn tại, nó sẽ cập nhật giá trị tương ứng. Điều này giúp đảm bảo rằng từ điển luôn chứa thông tin mới nhất mà không cần phải xóa hoặc thao tác phức tạp khác.

							
								
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
													
														int
													 dictAdd
													(
													dict
													 *
													d
													,
													 void
													 *
													key
													,
													 void
													 *
													val
													)
													
{
													
    dictEntry
													 *
													entry
													 =
													 dictAddRaw
													(
													d
													,
													key
													);
													

    if
													 (
													!
													entry
													)
													 return
													 DICT_ERR
													;
													
    dictSetVal
													(
													d
													,
													 entry
													,
													 val
													);
													
    return
													 DICT_OK
													;
													
}
													

dictEntry
													 *
													dictAddRaw
													(
													dict
													 *
													d
													,
													 void
													 *
													key
													)
													
{
													
    int
													 index
													;
													
    dictEntry
													 *
													entry
													;
													
    dictht
													 *
													ht
													;
													

    if
													 (
													dictIsRehashing
													(
													d
													))
													 _dictRehashStep
													(
													d
													);
													

    /* Get the index of the new elementđá gà trực tiếp app, or -1 if
     * the element already exists. */
    if
													 ((
													index
													 =
													 _dictKeyIndex
													(
													d
													,
													 key
													))
													 ==
													 -
													1
													)
													
        return
													 NULL
													;
													

    /* Allocate the memory and store the new entry.
     * Insert the element in top99WIN, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
    ht
													 =
													 dictIsRehashing
													(
													d
													)
													 ?
													 &
													d
													->
													ht
													[
													1
													]
													 :
													 &
													d
													->
													ht
													[
													0
													];
													
    entry
													 =
													 zmalloc
													(
													sizeof
													(
													*
													entry
													));
													
    entry
													->
													next
													 =
													 ht
													->
													table
													[
													index
													];
													
    ht
													->
													table
													[
													index
													]
													 =
													 entry
													;
													
    ht
													->
													used
													++
													;
													

    /* Set the hash entry fields. */
													
    dictSetKey
													(
													d
													,
													 entry
													,
													 key
													);
													
    return
													 entry
													;
													
}
													

static
													 int
													 _dictKeyIndex
													(
													dict
													 *
													d
													,
													 const
													 void
													 *
													key
													)
													
{
													
    unsigned
													 int
													 h
													,
													 idx
													,
													 table
													;
													
    dictEntry
													 *
													he
													;
													

    /* Expand the hash table if needed */
													
    if
													 (
													_dictExpandIfNeeded
													(
													d
													)
													 ==
													 DICT_ERR
													)
													
        return
													 -
													1
													;
													
    /* Compute the key hash value */
													
    h
													 =
													 dictHashKey
													(
													d
													,
													 key
													);
													
    for
													 (
													table
													 =
													 0
													;
													 table
													 <=
													 1
													;
													 table
													++
													)
													 {
													
        idx
													 =
													 h
													 &
													 d
													->
													ht
													[
													table
													].
													sizemask
													;
													
        /* Search if this slot does not already contain the given key */
													
        he
													 =
													 d
													->
													ht
													[
													table
													].
													table
													[
													idx
													];
													
        while
													(
													he
													)
													 {
													
            if
													 (
													key
													==
													he
													->
													key
													 ||
													 dictCompareKeys
													(
													d
													,
													 key
													,
													 he
													->
													key
													))
													
                return
													 -
													1
													;
													
            he
													 =
													 he
													->
													next
													;
													
        }
													
        if
													 (
													!
													dictIsRehashing
													(
													d
													))
													 break
													;
													
    }
													
    return
													 idx
													;
													
}
													

Đây là mã nguồn quan trọng của dictAdd. Chúng ta cần lưu ý các điểm chính sau:

  • Nó cũng sẽ kích hoạt tiến trình băm lại thêm một bước (_dictRehashStep).
  • Nếu đang trong quá trình băm lại99WIN, nó sẽ chèn dữ liệu vào ht[1]; nếu không, chèn vào ht[0].
  • Khi chèn dữ liệu vào bucket tương ứngđá gà trực tiếp app, dữ liệu luôn được thêm vào đầu củ Lý do là vì dữ liệu mới có khả năng sẽ được truy cập nhiều hơn trong tương lai, nhờ đó việc tìm kiếm lại dữ liệu này sau này sẽ chỉ cần ít lần so sánh hơn. Điều này giúp tối ưu hóa hiệu suất khi xử lý các hoạt động liên quan đến tìm kiếm và thao tác trên dữ liệu.
  • _key chỉ số trong _dictKeyIndex đang được sử dụng để tìm kiếm vị trí chèn. Nếu không phải trong quá trình tái phân tán hash99win club, nó chỉ kiểm tra trong bảng _ht[0]; ngược lại, nó sẽ kiểm tra cả hai bảng _ht[0] và _ht[1].
  • Biến _dictKeyIndex có thể kích hoạt việc mở rộng bộ nhớ của đối tượng dict (_dictExpandIfNeeded)đá gà trực tiếp app, trong đó kích thước của bảng băm sẽ được tăng gấp đôi. Để hiểu rõ hơn về cách thức hoạt động chi tiết, bạn có thể tham khảo mã nguồn trong tệp dict.c.

dictReplace được xây dựng dựa trên dictAddđá gà trực tiếp app, như sau:

							
								
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
													
														int
													 dictReplace
													(
													dict
													 *
													d
													,
													 void
													 *
													key
													,
													 void
													 *
													val
													)
													
{
													
    dictEntry
													 *
													entry
													,
													 auxentry
													;
													

    /* Try to add the element. If the key
     * does not exists dictAdd will suceed. */
													
    if
													 (
													dictAdd
													(
													d
													,
													 key
													,
													 val
													)
													 ==
													 DICT_OK
													)
													
        return
													 1
													;
													
    /* It already exists99WIN, get the entry */
    entry
													 =
													 dictFind
													(
													d
													,
													 key
													);
													
    /* Set the new value and free the old one. Note that it is important
     * to do that in this order99WIN, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    auxentry
													 =
													 *
													entry
													;
													
    dictSetVal
													(
													d
													,
													 entry
													,
													 val
													);
													
    dictFreeVal
													(
													d
													,
													 &
													auxentry
													);
													
    return
													 0
													;
													
}
													

Trong trường hợp key đã tồn tạiđá gà trực tiếp app, dictReplace sẽ gọi đồng thời cả dictAdd và dictFind, điều này thực chất giống như thực hiện hai lần quá trình tìm kiếm. Tại đây, mã nguồn của Redis chưa được tối ưu hóa một cách hiệu quả. Điều này có thể dẫn đến việc tiêu tốn thêm tài nguyên và làm chậm tốc độ xử lý khi số lượng phần tử trong từ điển tăng lên. Một giải pháp tối ưu hơn có thể là chỉ sử dụng dictFind để kiểm tra sự tồn tại của key và thực hiện hành động phù hợp thay vì chạy thêm dictAdd không cần thiết. Điều này không chỉ giúp giảm bớt gánh nặng cho hệ thống mà còn cải thiện hiệu suất tổng thể của Redis.

Xóa dict (dictDelete)

Mã nguồn của dictDelete ở đây bị bỏ quađá gà trực tiếp app, vui lòng tham khả c. Cần chú ý một chút là:

  • dictDelete cũng sẽ kích hoạt tiến trình băm lại thêm một bước (_dictRehashStep).
  • Nếu không đang ở giữa quá trình tái phân tán hashđá gà trực tiếp app, nó chỉ kiểm tra xem key cần xóa có tồn tại trong bảng ht[0] hay không; còn nếu đang trong quá trình tái phân tán hash, nó sẽ phải kiểm tra cả hai bảng ht[0] và ht[1] để tìm key đó.
  • Sau khi việc xóa thành công được thực hiện99win club, các hàm huỷ của key và value (keyDestructor và valDestructor) sẽ được gọi để giải phóng tài nguyên. Điều này giúp đảm bảo rằng mọi đối tượng không còn cần thiết đều được dọn dẹp một cách hiệu quả, tránh gây ra rò rỉ bộ nhớ hoặc các vấn đề liên quan đến tài nguyên khác trong hệ thống.

Việc triển khai của dict tương đối đơn giản99win club, bài viết này sẽ dừng lại ở đây. Ở bài viết tiếp theo, chúng ta sẽ tìm hiểu về việc triển khai chuỗi động trong Redis - sds, hãy cùng đón chờ nhé.


Bài viết gốcđá gà trực tiếp app, 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: /jzpzxx88.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: Làm người chính trong vở kịch cuộc đời — Đọc xong tác phẩm "Áo Đỏ"
Bài sau: Phân tích sâu cấu trúc dữ liệu nội bộ của Redis (2) —— sds