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

Chi tiết về cấu trúc dữ liệu bên trong Redis (1) —— dict


Chi tiết cấu trúc dữ liệu bên trong Redis

cấu trúc dữ liệu

Khía cạnh đầu tiênkeo banh, 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 cho bên ngoài.

Khía cạnh thứ haikết quả bóng đá việt nam hôm nay, từ góc độ thực hiện nội bộ, thuộc về triển khai ở tầng cơ sở 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 tập trung vào việc mức thứ haikết quả bóng đá việt nam hôm nay, đó là cách Redis thực hiện các cấu trúc dữ liệu bên trong và mối liên hệ giữa các cấu trúc dữ liệu ở hai cấp độ. Cụ thể, Redis sử dụng sự kết hợp của nhiều cấu trúc dữ liệu cơ bản ở mức thứ hai để xây dựng các cấu trúc dữ liệu cao cấp hơn ở mức đầu tiên. Qua đó, chúng ta có thể hiểu rõ hơn về cách Redis tối ưu hóa hiệu suất và tổ chức dữ liệu một cách linh hoạt, giúp nó trở thành một công cụ mạnh mẽ trong xử lý dữ liệu.

Khi thảo luận về việc thực hiện nội bộ của bất kỳ hệ thống nàokeo banh, điều quan trọng là chúng ta phải xác định rõ các nguyên tắc thiết kế trước. Điều này giúp chúng ta hiểu sâu hơn về ý định thực sự đằng sau những lựa chọn thiết kế đó. 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ố khía cạnh chính sau đây:

  • Hiệu quả bộ nhớ (memory efficiency) là một khía cạnh then chốt của Redis. Với vai trò chính là lưu trữ dữ liệukeo banh, Redis chủ yếu phụ thuộc vào bộ nhớ RAM để hoạt động. Do đó, việc tối ưu hóa và tiết kiệm bộ nhớ trở thành ưu tiên hàng đầu trong thiết kế Redis. Điều này đồng nghĩa với việc Redis đã được cẩn thận tối ưu hóa để nén dữ liệu một cách hiệu quả, giảm thiểu các mảnh vỡ bộ nhớ (memory fragmentation) và đảm bảo rằng tài nguyên máy tính được sử dụng một cách tối đa. Ngoài ra, Redis còn liên tục cập nhật các thuật toán mới nhằm cải thiện khả năng quản lý bộ nhớ, giúp nó luôn dẫn đầu trong việc xử lý dữ liệu nhanh chóng và hiệu quả.
  • Thời gian phản hồi nhanh (fast response time) là một trong những yếu tố quan trọng trong hệ thốngkết quả bóng đá việt nam hôm nay, và nó thường đối lập với khả năng xử lý dữ liệu lớn (high throughput). Redis được thiết kế để cung cấp truy cập trực tuyến, vì vậy việc tối ưu hóa thời gian đáp ứng cho mỗi yêu cầu là ưu tiên hàng đầu. Điều này có nghĩa là mục tiêu về thời gian phản hồi nhanh luôn được đánh giá cao hơn so với 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 mâu thuẫn nhau; một hệ thống có thể dễ dàng tăng tốc độ xử lý dữ liệu nếu chấp nhận hy sinh thời gian trả lời của từng yêu cầu, nhưng điều đó lại không phù hợp với nhu cầu của các ứng dụng yêu cầu phản hồi tức thì như Redis.
  • Redis hoạt động theo cơ chế đơn luồng (single-threaded)kết quả bóng đá việt nam hôm nay, điều này không có nghĩa là hiệu suất của nó bị giới hạn bởi tài nguyên CPU. Ngược lại, điểm nghẽn thực sự nằm ở tốc độ truy cập bộ nhớ và I/O mạng. Tuy nhiên, việc sử dụng mô hình đơn luồng lại mang lại một lợi thế lớn: nó giúp làm đơn giản hóa đáng kể việc triển khai các cấu trúc dữ liệu và thuật toán. Thay vào đó, Redis tận dụng các cơ chế như I/O bất đồng bộ và pipelining để đạt được khả năng truy cập song song nhanh chóng. Điều này cũng đặt ra yêu cầu cao hơn đối với thời gian phản hồi của từng yêu cầu riêng lẻ, vì mọi tác vụ đều được xử lý tuần tự trong cùng một luồng duy nhất.

Giải thích Chi tiết 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 sử dụng để duy trì mối quan hệ ánh xạ giữa key và valuekết quả bóng đá việt nam hôm nay, 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ệ từ key đến value trong một database đều được duy trì thông qua một dict. Tuy nhiên, đó chỉ là một trong số rất nhiều cách mà dict được sử dụ Ví dụ, khi một Redis hash có nhiều field, nó sẽ sử dụng dict để lưu trữ dữ liệu. Hay nữa, Redis kết hợp sử dụng dict và 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ẽ bàn thêm ở phần sau. Trong bài viết này, chúng ta sẽ tập trung nghiên cứu cách thức thực hiện 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 và value. Nó còn đóng vai trò như một cơ chế tối ưu hóa hiệu suất trong việc xử lý dữ liệu phức tạp. Khi một hash có số lượng field lớn, việc sử dụng dict giúp tăng tốc độ truy xuất dữ liệu. Điều này đặc biệt hữu ích trong môi trường mà các yêu cầu xử lý dữ liệu đòi hỏi độ chính xác cao và tốc độ nhanh chóng. Với sorted set, việc kết hợp dict và skiplist cho phép Redis cung cấp khả năng tìm kiếm và sắp xếp linh hoạt hơn, từ đó cải thiện hiệu quả hoạt động của hệ thống. Tuy vậy, để hiểu rõ hơn về cách dict hoạt động, chúng ta cần đi sâu vào các khía cạnh kỹ thuật. Đầu tiên, cần hiểu cơ chế cơ bản của dict, bao gồm cách phân bổ bộ nhớ, cách giải quyết xung đột giữa các key và cách tối ưu hóa quá trình tìm kiếm. Tất cả những yếu tố này đóng vai trò quan trọng trong việc đảm bảo Redis vận hành ổn định và hiệu quả. Chúng ta sẽ lần lượt khám phá từng khía cạnh này trong phần tiếp theo.

Dict cơ bản được thiết kế để giải quyết vấn đề tìm kiếm (Searching) trong các thuật toán. Thông thườngVSBET, cách tiếp cận để giải quyết vấn đề tìm kiếm có thể chia thành hai nhóm chính: một là dựa trên các cây cân bằng (balanced trees), và hai là dựa trên bảng băm (hash table). Những công cụ như Map hoặc dictionary mà chúng ta sử dụng hàng ngày chủ yếu được thực hiện dựa trên bảng băm. Trong trường hợp không cần dữ liệu được sắp xếp theo thứ tự và có thể duy trì xác suất xung đột băm ở mức thấp, hiệu suất tìm kiếm của phương pháp này sẽ rất cao, gần như đạt O(1), đồng thời dễ dàng triển khai và thực hiện hơn so với việc sử dụng cây. Ngoài ra, bảng băm còn giúp tối ưu hóa bộ nhớ và tốc độ xử lý nhờ khả năng ánh xạ trực tiếp giá trị đầu vào sang vị trí lưu trữ trong bộ nhớ. Điều này cho phép nó hoạt động nhanh chóng ngay cả khi lượng dữ liệu tăng lên đáng kể. Tuy nhiên, việc quản lý xung đột băm vẫn là một thách thức quan trọng cần được giải quyết để đảm bảo hiệu quả tổng thể.

thời gian phản hồi nhanh

Tiếp theo sẽ trình bày chi tiết.

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

Để thực hiện lại quá trình băm một cách tăng dần (incremental rehashing)kết quả bóng đá việt nam hôm nay, cấu trúc dữ liệu của dict chứa hai bảng băm. Trong quá trình này, dữ liệu sẽ được di chuyển từ bảng băm đầu tiên sang bảng băm thứ hai theo từng bước, giúp tối ưu hóa hiệu suất và đảm bảo tính ổn định trong quá trình hoạt động.

Định nghĩa mã C của dict như sau (trích từ nguồ h của 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 rehashingkeo banh, 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ể rõ ràng hơn về định nghĩa cấu trúc dữ liệu của dictkeo banh, chúng ta có thể sử dụng một biểu đồ cấu trúc để thể hiện nó. Dưới đây là hình ảnh.

Biểu đồ cấu trúc dict của Redis

Dựa trên mã nguồn và sơ đồ cấu trúc được đề cập ở trênkết quả bóng đá việt nam hôm nay, có thể dễ dàng nhận thấy cấu trúc của dict. Một dict bao gồm các thành phần sau đây: Trước hết, dict thường bắt đầu bằng một cặp dấu ngoặc nhọn {}, trong đó mỗi phần tử được phân cách bởi dấu phẩy (,). Mỗi phần tử trong dict bao gồm hai thành phần chính: key và value, được kết nối với nhau bằng dấu hai chấm (:). Điều này tạo nên một cặp giá trị duy nhấ Tiếp theo, key trong dict có thể là một chuỗi, số nguyên hoặc một số kiểu dữ liệu khác mà Python hỗ trợ, còn value có thể là bất kỳ kiểu dữ liệu nào, từ chuỗi, số cho đến cả list hay thậm chí là một dict khác. Điều này làm cho dict trở nên linh hoạt và mạnh mẽ trong việc lưu trữ thông tin. Ngoài ra, mỗi key trong dict phải là duy nhất. Nếu có hai key giống nhau được chỉ định, thì chỉ value của key cuối cùng sẽ được giữ lại. Điều này giúp tránh sự chồng chéo và đảm bảo tính nhất quán trong cấu trúc dữ liệu. Cuối cùng, dict không chỉ là một công cụ để lưu trữ dữ liệu, mà còn cung cấp nhiều phương thức hữu ích để thao tác với dữ liệu bên trong như thêm, xóa, tìm kiếm và chỉnh sửa các phần tử. Điều này làm cho dict trở thành một trong những kiểu dữ liệu quan trọng và phổ biến trong lập trình Python.

  • Một con trỏ hướng tới cấu trúc dictType (kiểu 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. Với sự linh hoạt nàykết quả bóng đá việt nam hôm nay, dict không còn bị giới hạn bởi các kiểu dữ liệu cố định, mở ra khả năng xử lý đa dạng các kiểu thông tin khác nhau một cách hiệu quả.
  • Một con trỏ dữ liệu riêng (privdata). Được truyền vào khi người gọi tạo dict.
  • Bạn có thể thấy hai bảng băm (ht[2]) trong ví dụ này. Chỉ khi quá trình băm lại (resizing) diễn rakết quả bóng đá việt nam hôm nay, cả ht[0] và ht[1] mới cùng lúc hoạt động hiệu quả. Còn thông thường, chỉ có ht[0] là được sử dụng, còn bảng ht[1] vẫn trống rỗng, không chứa bất kỳ dữ liệu nào. Hình ảnh bên trên minh họa trạng thái của quá trình băm lại ở một bước cụ thể giữa chừng.
  • Tham số rehashidx hiện tại cho biết trạng thái của quá trình tái phân tán (rehash). Nếu rehashidx bằng -1keo banh, có nghĩa là hệ thống không đang thực hiện bất kỳ hoạt động tái phân tán nào. Ngược lại, nếu giá trị khác -1, điều đó cho thấy quá trình tái phân tán đang diễn ra và giá trị này sẽ biểu thị bước hiện tại mà quá trình tái phân tán đã đạt đến.
  • Số lượng iterator đang được duyệt hiện tại. Điều này không phải là trọng tâm mà chúng ta đang thảo luậnkết quả bóng đá việt nam hôm nay, tạm thời bỏ qua.

Cấu trúc dictType bao gồm một số con trỏ đến hàmkết quả bóng đá việt nam hôm nay, cho phép người gọi thực hiện các thao tác tùy chỉnh liên quan đến key và Các thao tác này bao gồm: - Tạo và quản lý việc thêm, xóa, sửa các phần tử trong dict một cách linh hoạt. - So sánh và sắp xếp key theo thứ tự mong muốn, từ đó tối ưu hóa hiệu suất tìm kiếm. - Xác định cách mã hóa hoặc giải mã key và value để phù hợp với yêu cầu của ứng dụng. - Xử lý các trường hợp đặc biệt như va chạm key hoặc giá trị null. - Tùy chỉnh cách hiển thị hoặc lưu trữ dữ liệu trong dict theo chuẩn riêng. Các con trỏ đến hàm này cung cấp khả năng tùy biến cao, cho phép dict được điều chỉnh để đáp ứng tốt nhất nhu cầu cụ thể của từng ứng dụng.

  • hashFunctionVSBET, thuật toán băm để tính giá trị băm của key.
  • keyDup và valDup là hai hàm được định nghĩa riêng để sao chép key và valueVSBET, giúp tạo ra bản sao sâu (deep copy) thay vì chỉ truyền tham chiếu đến đối tượng. Điều này đặc biệt hữu ích khi bạn cần đảm bảo rằng giá trị hoặc key không bị ảnh hưởng bởi các thay đổi từ đối tượng gốc, nhờ đó tăng cường tính độc lập và độ an toàn trong xử lý dữ liệu.
  • keyComparekeo banh, xác định hoạt động so sánh giữa hai key, sẽ được sử dụng khi tìm kiế
  • Hàm keyDestructor và valDestructor sẽ được sử dụng riêng biệt để xác định cách xử lý việc hủy bỏ (xây dựng quá trình giải phóng bộ nhớ hoặc tài nguyên) cho khóa (key) và giá trị (value). Hai hàm này đóng vai trò quan trọng trong việc đảm bảo rằng mọi tài nguyên liên quan đến khóa và giá trị đều được giải phóng một cách an toàn và hiệu quả sau khi chúng không còn cần thiết. Điều này giúp duy trì tính ổn định và hiệu suất của chương trìnhkeo banh, đồng thời tránh các sự cố về bộ nhớ như rò rỉ tài nguyên hoặc lỗi truy cập bất hợp pháp.

Con trỏ dữ liệu riêng tư (privdata) là một tham số được trả về cho người gọi khi một số hoạt động của dictType được kích hoạt. Đây thường là một cơ chế giúp lưu trữ thông tin bổ sung hoặc ngữ cảnh mà các hàm xử lý cần sử dụng trong quá trình thực thikết quả bóng đá việt nam hôm nay, từ đó tăng tính linh hoạt và khả năng tùy chỉnh cho cấu trúc dữ liệu.

Cần xem xét kỹ hơn cấu trúc dictht. Nó định nghĩa cấu trúc của một bảng bămkeo banh, bao gồm một số mục sau:

  • Trong một mảng con trỏ dictEntry (gọi là bảng băm - table)kết quả bóng đá việt nam hôm nay, giá trị hash của key cuối cùng sẽ ánh xạ đến một vị trí cụ thể trong mảng này, tương ứng với một bucket. Tuy nhiên, nếu nhiều key khác nhau cùng ánh xạ đến cùng một vị trí, hiện tượng va chạm (collision) sẽ xảy ra. Khi đó, các phần tử này sẽ được lưu trữ theo dạng một danh sách liên kết (linked list) của dictEntry tại vị trí đó.
  • size: Nhận dạng độ dài của mảng các con trỏ Nó luôn là một lũy thừa của 2.
  • thuộc tính sizemask được sử dụng để ánh xạ giá trị băm (hash value) tới vị trí tương ứng trong bảng (table). Giá trị của sizemask bằng (size - 1)kết quả bóng đá việt nam hôm nay, ví dụ như 7, 15, 31, 63, v.v., 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 xử lý qua hàm băm (hashFunction) để tạo ra một giá trị băm, sau đó giá trị này sẽ được tính toán thông qua phép AND với sizemask để xác định vị trí cụ thể trong bảng. Điều này tương đương với việc thực hiện phép chia lấy dư (hash value % size) để xác định vị trí tương ứng trong bảng.
  • Trong ngữ cảnh nàykeo banh, từ "used" được sử dụng để đếm số lượng mục hiện có Tỷ lệ giữa giá trị "used" và "size" chính là hệ số tải (load factor). Khi tỷ lệ này càng lớn, xác suất xảy ra xung đột giá trị băm (hash collision) cũng sẽ tăng lên theo cấp số nhân. Điều này ảnh hưởng trực tiếp đến hiệu suất của cấu trúc dữ liệu, vì xung đột nhiều sẽ làm giảm tốc độ truy xuất thông tin. Vì vậy, việc quản lý cẩn thận hệ số tải là vô cùng quan trọng để đảm bảo hiệu quả hoạt động tối ư

Trong cấu trúc dictEntrykeo banh, có chứa 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 biến v là một union (hợp nhất), khi giá trị của nó là uint64_t, int64_t hoặc double, thì không cần phải sử dụng thêm bộ nhớ bổ sung, điều này giúp giảm thiểu tình trạng phân mảnh 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. Thêm vào đó, việc sử dụng union trong trường hợp này không chỉ tối ưu hóa không gian bộ nhớ mà còn tạo ra sự linh hoạt trong việc quản lý các loại dữ liệu khác nhau. Union cho phép cùng một vùng nhớ được sử dụng để lưu trữ nhiều kiểu dữ liệu khác nhau, tùy thuộc vào cách sử dụng cụ thể. Điều này làm cho dictEntry trở nên hiệu quả hơn trong việc xử lý các tập dữ liệu đa dạng mà không bị giới hạn bởi kích thước cố định của từng loại dữ liệu riêng lẻ.

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ị khởi tạo cho các biến liên quan. Trong đókết quả bóng đá việt nam hôm nay, hai bảng băm ht[0] và ht[1] ban đầu chưa được phân bổ không gian lưu trữ, cả hai con trỏ table đều được gán giá trị NULL. Điều này có nghĩa là không gian thực sự chỉ được phân bổ khi dữ liệu đầu tiên được thêm vào. Việc trì hoãn việc phân bổ này giúp tối ưu hóa tài nguyên và tăng hiệu suất trong quá trình sử dụng.

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 đâykết quả bóng đá việt nam hôm nay, dựa trên trạng thái hiện tại của dict đang tiến hành rehashing, lần lượt thực hiện những việc sau:

  • Nếu quá trình rehashing (thay thế hash) đang diễn rakeo banh, hãy tiến hành một bước tiếp theo trong quá trình này (tức là gọi hàm _dictRehashStep). Trên thực tế, ngoài việc tìm kiếm, các thao tác chèn và xóa cũng có thể kích hoạt hành động này. Điều này giúp phân tán quá trình thay thế 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, từ đó giảm tải và cải thiện hiệu suất của hệ thống.
  • Bạn có thể tính toán giá trị băm của key (gọi hàm dictHashKeyVSBET, trong đó sẽ gọi đến hàm hashFunction đã được đề cập trước đó). Hàm này sẽ xử lý và trả về giá trị băm dựa trên thuật toán đã được cài đặt sẵn, đảm bảo tính hiệu quả và chính xác khi lưu trữ hoặc tìm kiếm dữ liệu trong cấu trúc từ điển.
  • Bạn có thể bắt đầu bằng cách tìm kiếm trong bảng băm đầu tiênkết quả bóng đá việt nam hôm nay, ht[0]. Đầu tiên, bạn cần xác định vị trí tương ứng với giá trị băm trong mảng table, như đã giải thích trước đó, bằng cách sử dụng phép AND logic giữa giá trị băm và sizemask. Sau khi xác định được vị trí này, hãy 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, bạn sẽ so sánh key; để làm điều này, hãy gọi hàm dictCompareKeys, mà bên trong nó sẽ gọi đến hàm keyCompare đã đề cập trước đó. Nếu tìm thấy mục cần tìm, hãy trả về giá trị đó. Nếu không, hãy chuyển sang bước tiếp theo.
  • Bạn có thể xác định liệu hiện tại có đang ở trong quá trình tái phân tán (rehashing) hay không. Nếu khôngkeo banh, kết quả tìm kiếm trên bảng hash 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 ở giai đoạn tái phân tán, hãy tiến hành tìm kiếm trên bảng hash ht[1] (thực hiện theo cùng một quy trình như bước trước đó).

Chúng ta cần xem xét kỹ hơn cách thực hiện của _dictRehashStep để rehashing 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
													;
													
}
													

Mỗi lần thực hiện dictRehashkeo banh, nó sẽ tiến hành ít nhất n bước tái phân tán (trừ khi toàn bộ quá trình tái phân tán kết thúc trong vòng chưa đầy n bước), và mỗi bước sẽ di chuyển từng phần tử dictEntry của một bucket (một danh sách liên kết dictEntry) từ bảng ht[0] sang bảng ht[1]. Vị trí mới của các phần tử này trên ht[1] sẽ được tính toán lại dựa trên sizemask của ht[1]. Tham số rehashidx sẽ lưu giữ vị trí hiện tại của bucket còn lại trong ht[0] mà vẫn chưa được di chuyển (các bucket đang chờ được xử lý). Quá trình này giúp tối ưu hóa việc quản lý bộ nhớ và đảm bảo rằng dữ liệu trong bảng hash luôn được sắp xếp lại một cách hợp lý khi kích thước của bảng hash thay đổi.

Khi hàm dictRehash được thực hiện và biến rehashidx đang trỏ đến một bucket trong bảng hash mà không có bất kỳ dictEntry nàoVSBET, điều đó có nghĩa là không có dữ liệu nào cần phải di chuyển. Lúc này, hệ thống sẽ cố gắng lặp qua mảng ht[0].table để tìm kiếm vị trí tiếp theo của bucket chứa dữ liệu. Nếu không tìm thấy bucket nào như vậy sau khi đã di chuyển qua nhiều lần, nó sẽ dừng lại sau tối đa n * 10 bước. Quá trình tái phân tán hash trong lần này sẽ tạm thời kết thúc. Nếu không có gì thay đổi, việc tìm kiếm các bucket chứa dữ liệu sẽ được tiếp tục trong các lần tái phân tán tiếp theo. Điều này đảm bảo rằng quá trình này không bị treo vô hạn ở một trạng thái không thể giải quyết, đồng thời duy trì hiệu suất hoạt động của hệ thống.

Cuối cùngkeo banh, nếu tất cả dữ liệu trên ht[0] đã được di chuyể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ẽ có nội dung giống như 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 phân tán (rehashing) ở trênVSBET, ta dễ dàng nhận thấy 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 thành công sang ht[1]. Ngoài ra, qua quá trình quan sát kỹ lưỡng, chúng ta có thể thấy rằng việc tái phân phối này không chỉ đơn thuần là việc dịch chuyển các bucket mà còn liên quan đến việc đảm bảo tính cân bằng và hiệu quả trong việc quản lý bộ nhớ. Điều này giúp hệ thống tránh được hiện tượng quá tải tại một số vị trí nhất định, từ đó nâng cao hiệu suất hoạt động tổng thể.

Chèn dict (dictAdd và dictReplace)

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

Tương tự như việc chèn một cặp key và valueVSBET, dictReplace sẽ cập nhật giá trị value nếu key đã tồn tại trong từ điển. Điều này giúp bạn dễ dàng điều chỉnh hoặc thay thế các giá trị hiện có mà không cần xóa và tạo lại toàn bộ mụ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 elementkeo banh, 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 topkết quả bóng đá việt nam hôm nay, 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 ý một số điểm chính sau đây:

  • Nó cũng sẽ kích hoạt bước rehashing tiếp theo (_dictRehashStep).
  • Nếu đang ở giữa quá trình rehashingVSBET, nó sẽ chèn dữ liệu vào ht[1]; nếu không thì chèn vào ht[0].
  • Khi thêm dữ liệu vào bucket tương ứngVSBET, dữ liệu luôn được chèn vào đầu củ Lý do là vì khả năng dữ liệu mới được truy cập tiếp theo sẽ cao hơn, nhờ đó việc tìm kiếm lại nó sau này sẽ yêu cầu ít lần so sánh hơn. Thêm vào đó, cách sắp xếp này không chỉ tối ưu hóa hiệu suất tìm kiếm mà còn giúp giảm thiểu thời gian xử lý cần thiết cho mỗi hoạt động tra cứu. Điều này đặc biệt quan trọng trong các hệ thống lớn, nơi tốc độ và hiệu quả đóng vai trò quyết định đối với khả năng hoạt động ổn định.
  • _keyIndexDict sẽ tìm kiếm vị trí chèn trong từ điển. Nếu không đang ở quá trình tái phân tánkết quả bóng đá việt nam hôm nay, nó chỉ kiểm tra ht[0]; ngược lại, nó sẽ kiểm tra cả 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` (thông qua hàm `_dictExpandIfNeeded`VSBET, hàm này sẽ tăng gấp đôi kích thước bảng băm. Để biết thêm chi tiết, bạn vui lòng tham khảo mã nguồn trong tệp `dict.c`).

dictReplace được xây dựng dựa trên dictAddVSBET, 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 existsVSBET, 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 orderkết quả bóng đá việt nam hôm nay, 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ạiVSBET, dictReplace sẽ đồng thời gọi đến cả dictAdd và dictFind, điều này về cơ bản 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 hoàn toàn. Có thể nói rằng việc thực hiện cả hai hàm dictAdd và dictFind cùng lúc là không cần thiết khi key đã được xác định là đã tồn tại trong từ điển, dẫn đến việc tiêu tốn thêm tài nguyên và làm chậm hiệu suất tổng thể của hệ thống. Một cải tiến nhỏ có thể giúp giảm thiểu số lượng thao tác lặp lại và tăng tốc độ xử lý trong các trường hợp như vậy.

Xóa dict (dictDelete)

Mã nguồn của dictDelete ở đây được bỏ quaVSBET, xin vui lòng tham khảo dict.c. Cần chú ý một chút:

  • dictDelete cũng sẽ kích hoạt bước rehashing tiếp theo (_dictRehashStep).
  • Nếu không đang ở giữa quá trình tái phân tán (rehashing)keo banh, nó sẽ chỉ tìm kiếm key cần xóa trong bảng ht[0]. Tuy nhiên, nếu đang trong quá trình tái phân tán, nó sẽ phải kiểm tra cả hai bảng ht[0] và ht[1] để tìm key đó. Đây là một bước quan trọng để đảm bảo rằng dữ liệu được xử lý chính xác trong mọi trạng thái của cấu trúc bảng hash.
  • Sau khi xóa thành côngkeo banh, 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 đều được dọn dẹp một cách gọn gàng và hiệu quả sau khi không còn cần thiết nữa.

Việc triển khai của dict tương đối đơn giảnkết quả bóng đá việt nam hôm nay, bài viết này sẽ dừng lại ở đây. Ở bài viết tiếp theo, chúng ta sẽ cùng 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ốckết quả bóng đá việt nam hôm nay, 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 này: /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 tôi "Trương Thiết Lệ" trên Weibo.
Tài khoản WeChat của tôi: tielei-blog (Trương Thiết Lệ)
Bài trước: Làm người chính trong vở kịch cuộc đời — Đọc xong “Áo Đỏ”
Bài sau: Chi tiết về cấu trúc dữ liệu bên trong Redis (2) —— sds