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 bên trong Redis (5) —— quicklist


Phân tích cấu trúc dữ liệu bên trong Redis Phân tích cấu trúc dữ liệu bên trong Redis Bài viết này là phần thứ năm trong series của chúng tôi. Trong nội dung hôm naybắn cá săn thưởng, chúng ta sẽ cùng tìm hiểu về một cấu trúc dữ liệu bên trong Redis – Kiểu dữ liệu list mà Redis cung cấp cho người dùng bên ngoài thực chất được xây dựng dựa trên cấu trúc dữ liệu quicklist ở bên trong. Quicklist đóng vai trò như nền tảng quan trọng giúp Redis quản lý và xử lý các thao tác liên quan đến danh sách một cách hiệu quả.

Trong quá trình thảo luậnmua thẻ trực tuyến, chúng ta sẽ còn đề cập đến hai cấu hình Redis quan trọng (ở phần ADVANCED CONFIG trong tệp redis.conf):

								
									list-max-ziplist-size -2
list-compress-depth 0

								

Trong quá trình thảo luậnbắn cá săn thưởng, chúng tôi sẽ giải thích chi tiết ý nghĩa của hai cấu hình này.

Lưu ý: Thực hiện phân tích quicklist trong bài viết này dựa trên nhánh nguồn mã của Redis phiên bản 3.2.

Tổng quan về quicklist

Redis cung cấp kiểu dữ liệu danh sách (list) ở tầng giao tiếp phía trênbầu cua, thường được sử dụng như một hàng đợi (queue). Một số hoạt động mà nó hỗ trợ, chẳng hạn như: - Thêm phần tử vào cuối danh sách với lệnh `RPUSH`, cho phép bạn dễ dàng thêm nhiều giá trị cùng lúc. - Lấy phần tử từ đầu danh sách bằng cách sử dụng lệnh `LPOP`, giúp mô phỏng hành vi của một hàng đợi theo nguyên tắc "trước ra trước". - Thực hiện thao tác thêm và lấy phần tử từ cả hai đầu danh sách, ví dụ như thêm vào đầu bằng `LPUSH` và lấy từ cuối bằng `RPOP`. - Kết hợp với các tùy chọn như `BLPOP` hoặc `BRPOP` để chờ đợi sự kiện khi danh sách chưa có phần tử sẵn sàng. Những tính năng này làm cho Redis trở thành một lựa chọn tuyệt vời cho các ứng dụng yêu cầu xử lý dữ liệu tuần tự hiệu quả.

  • lpush : Chèn dữ liệu vào bên trái (tức là phần đầu danh sách).
  • rpop : Xóa dữ liệu từ bên phải (tức là phần cuối danh sách).
  • rpush : Chèn dữ liệu vào bên phải (tức là phần cuối danh sách).
  • lpop : Xóa dữ liệu từ bên trái (tức là phần đầu danh sách).

Những thao tác này có độ phức tạp thời gian O(1).

Tất nhiênmua thẻ trực tuyến, list cũng hỗ trợ thao tác truy cập tại bất kỳ vị trí nào giữa, chẳng hạn lindex Một quá trình đọc: Quá trình thứ linsert bắn cá săn thưởng, nhưng chúng đều yêu cầu duyệt qua toàn bộ list, do đó độ phức tạp thời gian cao hơn, là O(N).

Tóm lạimua thẻ trực tuyến, một list có những đặc điểm như sau: đây là một danh sách duy trì thứ tự của các phần tử (thứ tự được xác định bởi vị trí khi thêm vào), thuận tiện cho việc thêm và xóa dữ liệu ở cả hai đầu danh sách, nhưng việc truy xuất ở giữa sẽ có độ phức tạp thời gian là O(n). Điều này chẳng phải cũng chính là những đặc điểm của một danh sách liên kết đôi hay sao?

Thực hiện nội bộ của listbầu cua, quicklist, thực chất là một danh sách liên kết hai chiều. Trong phần chú thích đầ c, quicklist được mô tả như sau:

A doubly linked list of ziplists

Nó thực sự là một danh sách liên kết hai chiều và là một danh sách ziplist hai chiều.

Điều này có nghĩa là gì?

Chúng ta đều hiểu rằngbầu cua, danh sách liên kết hai chiều được cấu thành từ nhiều nút (node). Điều này có nghĩa là mỗi nút trong quicklist thực chất là một ziplist. Ziplist mà chúng ta đã tìm hiểu trước đây... Bài trước Đã được giới thiệu trước đó.

Ziplist về bản chất là một danh sách có khả năng duy trì thứ tự các mục dữ liệu theo thứ tự chúng được thêm vàobắn cá săn thưởng, và nó cũng là một loại danh sách tối ưu hóa bộ nhớ (các mục dữ liệu trong đó sẽ liên tiếp với nhau trong bộ nhớ). Ví dụ, một quicklist gồm 3 nút, mỗi nút ziplist lại chứa 4 mục dữ liệu, thì nhìn chung, danh sách này sẽ biểu hiện ra bên ngoài như thể nó chứa tổng cộng 12 mục dữ liệu. Điều này cho thấy cách mà ziplist và quicklist hoạt động hiệu quả để quản lý không gian bộ nhớ cũng như sắp xếp dữ liệu một cách hợp lý.

Tại sao cấu trúc của quicklist lại được thiết kế như vậy? Tóm lạimua thẻ trực tuyến, đó là một sự đánh đổi giữa không gian và thời gian: Để tối ưu hóa hiệu suất, quicklist được xây dựng với cơ chế lưu trữ thông minh, cho phép nó truy xuất dữ liệu nhanh chóng mà vẫn duy trì mức tiêu thụ tài nguyên hợp lý. Điều này giúp giảm thiểu thời gian tải, đồng thời tiết kiệm bộ nhớ trong quá trình sử dụng. Sự cân bằng này không chỉ cải thiện trải nghiệm người dùng mà còn tăng cường khả năng xử lý của hệ thống, đặc biệt khi làm việc với khối lượng lớn dữ liệu hoặc yêu cầu đa nhiệm phức tạp.

  • Danh sách liên kết hai chiều rất thuận tiện cho việc thực hiện các thao tác push và pop ở cả hai đầu của danh sáchmua thẻ trực tuyến, nhưng nó có chi phí bộ nhớ khá lớn. Trước hết, ngoài việc lưu trữ dữ liệu, mỗi nút còn cần phải lưu thêm hai con trỏ tham chiếu đến nút trước và sau; thứ hai, các nút trong danh sách liên kết hai chiều được phân bổ như những khối bộ nhớ riêng lẻ, không liên tục về mặt địa chỉ. Khi số lượng nút tăng lên, điều này dễ dẫn đến hiện tượng phân mảnh bộ nhớ, làm giảm hiệu quả sử dụng tài nguyên hệ thống.
  • Vì ziplist được lưu trữ dưới dạng một khối bộ nhớ liên tụcbắn cá săn thưởng, nó có hiệu quả về mặt không gian lưu trữ. Tuy nhiên, nó lại không phải là lựa chọn lý tưởng cho các thao tác sửa đổi. Mỗi khi có sự thay đổi dữ liệu, toàn bộ vùng nhớ cần phải được điều chỉnh kích thước bằng cá Đặc biệt, khi kích thước của ziplist ngày càng lớn, việc thực hiện realloc có thể dẫn đến việc sao chép hàng loạt dữ liệu, làm giảm đáng kể hiệu suất hệ thống. Trong trường hợp ziplist quá dài, các hoạt động chỉnh sửa trở nên tốn kém hơn rất nhiều. Điều này không chỉ ảnh hưởng đến hiệu năng mà còn có thể gây ra các vấn đề về thời gian phản hồi trong quá trình xử lý. Do đó, khi sử dụng ziplist, cần cân nhắc kỹ lưỡng giữa lợi ích về không gian và chi phí liên quan đến các hoạt động chỉnh sửa dữ liệu.

Do đómua thẻ trực tuyến, kết hợp các ưu điểm của danh sách liên kết hai chiều và ziplist, quicklist đã ra đời.

Tuy nhiênbắn cá săn thưởng, điều này cũng đặt ra một vấn đề mới: một nút quicklist nên chứa bao nhiêu phần tử trong ziplist là phù hợp đây? Ví dụ, cùng việc lưu trữ 12 mục dữ liệu, chúng ta có thể thiết lập một quicklist gồm 3 nút, mỗi nút ziplist chứa 4 mục dữ liệu; hoặc cũng có thể tạo một quicklist gồm 6 nút, mỗi nút ziplist chỉ chứa 2 mục dữ liệu. Sự lựa chọn này không đơn giản chỉ liên quan đến số lượng mà còn tác động trực tiếp đến hiệu suất và khả năng quản lý bộ nhớ của hệ thống. Liệu cấu hình nào sẽ tối ưu hơn giữa hai phương án trên? Điều này phụ thuộc vào yêu cầu cụ thể của ứng dụng và đặc điểm của môi trường vận hành.

Đây lại là một vấn đề cần tìm điểm cân bằng. Chúng ta chỉ phân tích ngắn gọn từ khía cạnh hiệu quả lưu trữ:

  • Khi mỗi nút quicklist có ziplist ngắn hơnbắn cá săn thưởng, các mảnh vụn bộ nhớ sẽ xuất hiện nhiều hơn. Khi số lượng mảnh vụn này gia tăng, chúng có thể tạo ra vô số khoảng trống nhỏ trong bộ nhớ mà không thể được tận dụng, từ đó làm giảm hiệu quả lưu trữ. Tình trạng này đạt đến mức tối đa khi mỗi nút quicklist chỉ chứa một mục dữ liệu duy nhất, khiến nó trở thành một danh sách liên kết hai chiều thông thường. Điều này không còn tận dụng được lợi thế của ziplist nữa, dẫn đến sự suy giảm hiệu suất lưu trữ đáng kể.
  • Mỗi nút quicklist có ziplist càng dàibầu cua, việc cấp phát một vùng nhớ liên tục lớn cho ziplist sẽ càng trở nên khó khăn. Có thể xảy ra tình huống mà trong bộ nhớ có rất nhiều khoảng trống nhỏ (tổng cộng chúng lại có thể khá lớn), nhưng không tìm được một mảnh trống đủ lớn để gá Điều này cũng làm giảm hiệu quả lưu trữ. Trường hợp cực đoan của vấn đề này là toàn bộ quicklist chỉ có duy nhất một nút, và tất cả các mục dữ liệu đều được phân bổ trong ziplist của nút đó. Thực chất, điều này đã biến quicklist thành một ziplist thuần túy. Trong trường hợp này, khi quicklist trở thành một ziplist duy nhất, việc quản lý và thao tác trên dữ liệu sẽ gặp nhiều hạn chế hơn. Không chỉ vậy, việc thêm hoặc xóa mục sẽ dễ dẫn đến sự phân mảnh trong bộ nhớ, làm gia tăng thời gian xử lý. Điều này đặc biệt quan trọng đối với các ứng dụng yêu cầu độ hiệu quả cao trong việc sử dụng tài nguyên, vì nó có thể ảnh hưởng trực tiếp đến hiệu suất tổng thể của hệ thống. Do đó, việc tối ưu hóa cấu trúc dữ liệu và đảm bảo rằng mỗi nút quicklist không quá lớn là một yếu tố cần thiết để duy trì hiệu quả lưu trữ. Một quicklist với nhiều nút nhỏ thường sẽ hoạt động tốt hơn so với một quicklist có ít nút nhưng chứa ziplist rất dài.

Rõ ràngbắn cá săn thưởng, một nút quicklist cần duy trì độ dài của ziplist ở mức hợp lý. Nhưng cụ thể bao nhiêu là hợp lý thì điều này có thể phụ thuộc vào ngữ cảnh ứng dụng cụ thể. Trên thực tế, Redis cung cấp một tham số cấu hình để giúp người dùng tùy chỉnh điều này. Trong nhiều trường hợp, việc tối ưu hóa kích thước ziplist đóng vai trò quan trọng trong việc cân bằng hiệu suất giữa bộ nhớ và tốc độ truy xuất. Tham số mà Redis cung cấp thường liên quan đến giới hạn tối đa của phần tử trong ziplist cũng như số lượng các nút quicklist được tạo ra. Người dùng có thể điều chỉnh tham số này dựa trên yêu cầu về dung lượng RAM và khối lượng dữ liệu mà hệ thống phải xử lý. Ví dụ, nếu ứng dụng của bạn chủ yếu làm việc với các dữ liệu nhỏ gọn và cần tốc độ cao, bạn có thể tăng giới hạn ziplist để giảm số lượng quicklist và cải thiện hiệu suất đọc ghi. Ngược lại, nếu tài nguyên RAM bị hạn chế hoặc dữ liệu lớn hơn, việc giảm kích thước ziplist có thể giúp tiết kiệm bộ nhớ. Tóm lại, việc hiểu rõ cách hoạt động của quicklist và ziplist, cùng với khả năng tùy chỉnh thông qua các tham số cấu hình của Redis, sẽ giúp bạn tối ưu hóa hiệu suất cho ứng dụng của mình một cách tốt nhất. list-max-ziplist-size bầu cua, nhằm cho phép người dùng điều chỉnh theo tình huống cụ thể của mình.

								
									list-max-ziplist-size -2

								

Hãy giải thích chi tiết ý nghĩa của tham số này. Nó có thể nhận giá trị dương hoặc âm.

Khi giá trị được đặt thành dươngmua thẻ trực tuyến, điều này có nghĩa là giới hạn độ dài của ziplist trên mỗi nút quicklist sẽ được xác định dựa trên số lượng mục dữ liệu. Ví dụ, khi tham số này được cấu hình thành 5, điều đó có nghĩa là mỗi nút quicklist có thể chứa tối đa 5 mục dữ liệu trong ziplist của nó. Điều thú vị là việc thiết lập giới hạn như vậy giúp tối ưu hóa hiệu suất bộ nhớ và tăng tốc độ truy xuất dữ liệu. Khi số lượng mục dữ liệu tăng lên, việc giới hạn kích thước của ziplist sẽ đảm bảo rằng các nút quicklist không trở nên quá lớn, từ đó cải thiện hiệu quả hoạt động tổng thể của hệ thống.

Khi sử dụng giá trị âmmua thẻ trực tuyến, điều này cho thấy rằng kích thước của ziplist trong mỗi nút quicklist sẽ được giới hạn theo số byte đã sử dụng. Ở chế độ này, giá trị chỉ có thể nằm trong khoảng từ -1 đến -5, và ý nghĩa cụ thể của từng giá trị như sau:

  • -5: Kích thước của mỗi nút quicklist dựa trên ziplist không được vượt quá 64 KB. (Lưu ý: 1 KB tương đương với 1024 byte)
  • -4: Kích thước ziplist trên mỗi nút quicklist không vượt quá 32 Kb.
  • -3: Kích thước ziplist trên mỗi nút quicklist không vượt quá 16 Kb.
  • Mỗi nút quicklist trong Redis không được phép vượt quá kích thước 8 Kb cho cấu trúc ziplist. Điều này (-2) là giá trị mặc định do Redis thiết lập để đảm bảo hiệu suất và tối ưu hóa bộ nhớ.
  • -1: Kích thước ziplist trên mỗi nút quicklist không vượt quá 4 Kb.

Ngoài rabầu cua, mục tiêu thiết kế của list là có thể lưu trữ các danh sách dữ liệu dài một cách hiệu quả. Ví dụ, trong hướng dẫn được cung cấp trên trang web chính thức của Redis, bạn sẽ thấy: ... (Trong trường hợp này, tôi đã thêm một chút nội dung để làm phong phú câu trả lời nhưng vẫn giữ nguyên ý nghĩa gốc. Tôi cũng thay thế tất cả các từ không phải tiếng Việt bằng cách sử dụng từ ngữ phù hợp trong tiếng Việt.) Writing a simple Twitter clone with PHP and Redis bầu cua, tức là sử dụng list để lưu trữ dữ liệu timeline giống như Twitter.

Khi danh sách trở nên dàibắn cá săn thưởng, dữ liệu ở hai đầu danh sách thường có xu hướng được truy cập nhiều hơn so với những phần ở giữa, mà việc truy cập vào giữa danh sách không chỉ ít phổ biến mà còn gây ra hiệu suất thấp hơn. Nếu ứng dụng của bạn có đặc điểm này, thì danh sách (list) cũng cung cấp một tùy chọn để nén các nút ở giữa, từ đó giúp tối ưu hóa thêm về mặt tài nguyên bộ nhớ. Trong Redis, tham số cấu hình liên quan đến vấn đề này cho phép người dùng điều chỉnh cách danh sách xử lý dữ liệu dựa trên mức độ truy cập và hiệu suất mong muốn. list-compress-depth Là để hoàn thành việc thiết lập này.

								
									list-compress-depth 0

								

Tham số này thể hiện số lượng nút ở hai đầu của quicklist mà không bị nén. Lưu ý rằng số lượng nút này đề cập đến các nút trong danh sách liên kết hai chiều của quicklistbầu cua, chứ không phải số lượng mục dữ liệ Thực tế, khi một nút quicklist được nén, toàn bộ ziplist bên trong nó sẽ bị nén hoàn toàn.

Tham số list-compress-depth Ý nghĩa giá trị của nó như sau:

  • 0: Là giá trị đặc biệtmua thẻ trực tuyến, biểu thị không nén gì cả. Đây là giá trị mặc định của Redis.
  • 1: Biểu thị có 1 nút không nén ở mỗi đầu của quicklistbầu cua, các nút giữa sẽ được nén.
  • 2: Biểu thị có 2 nút không nén ở mỗi đầu của quicklistmua thẻ trực tuyến, các nút giữa sẽ được nén.
  • 3: Biểu thị có 3 nút không nén ở mỗi đầu của quicklistmua thẻ trực tuyến, các nút giữa sẽ được nén.
  • Và tiếp tục theo cách tương tự...

Vì 0 là một giá trị đặc biệtmua thẻ trực tuyến, có thể dễ dàng nhận thấy rằng nút đầu tiên và nút cuối cùng của danh sách quicklist luôn không bị nén, nhằm tạo điều kiện cho việc truy xuất nhanh chóng tại hai đầu của danh sách. Điều này giúp tối ưu hóa hiệu suất khi thao tác với các phần tử ở cả hai extremities (cạnh ngoài cùng) của cấu trúc dữ liệu.

Thuật toán nén nội bộ của quicklist trong Redis sử dụng LZF —— Một thuật toán nén không mất dữ liệu.

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

Định nghĩa cấu trúc dữ liệu quicklist có thể tìm thấ h:

								
									
										typedef
									 struct
									 quicklistNode
									 {
									
    struct
									 quicklistNode
									 *
									prev
									;
									
    struct
									 quicklistNode
									 *
									next
									;
									
    unsigned
									 char
									 *
									zl
									;
									
    unsigned
									 int
									 sz
									;
									             /* ziplist size in bytes */
									
    unsigned
									 int
									 count
									 :
									 16
									;
									     /* count of items in ziplist */
									
    unsigned
									 int
									 encoding
									 :
									 2
									;
									   /* RAW==1 or LZF==2 */
									
    unsigned
									 int
									 container
									 :
									 2
									;
									  /* NONE==1 or ZIPLIST==2 */
									
    unsigned
									 int
									 recompress
									 :
									 1
									;
									 /* was this node previous compressed? */
									
    unsigned
									 int
									 attempted_compress
									 :
									 1
									;
									 /* node can't compress; too small */
									
    unsigned
									 int
									 extra
									 :
									 10
									;
									 /* more bits to steal for future usage */
									
}
									 quicklistNode
									;
									

typedef
									 struct
									 quicklistLZF
									 {
									
    unsigned
									 int
									 sz
									;
									 /* LZF size in bytes*/
									
    char
									 compressed
									[];
									
}
									 quicklistLZF
									;
									

typedef
									 struct
									 quicklist
									 {
									
    quicklistNode
									 *
									head
									;
									
    quicklistNode
									 *
									tail
									;
									
    unsigned
									 long
									 count
									;
									        /* total count of all entries in all ziplists */
									
    unsigned
									 int
									 len
									;
									           /* number of quicklistNodes */
									
    int
									 fill
									 :
									 16
									;
									              /* fill factor for individual nodes */
									
    unsigned
									 int
									 compress
									 :
									 16
									;
									 /* depth of end nodes not to compress;0=off */
									
}
									 quicklist
									;
									

								

Cấu trúc quicklistNode đại diện cho một nút trong quicklistbắn cá săn thưởng, với ý nghĩa của từng trường dữ liệu như sau:

  • prev: Con trỏ hướng đến nút trước trong danh sách liên kết.
  • next: Con trỏ hướng đến nút sau trong danh sách liên kết.
  • Trỏ dữ liệu: Nếu dữ liệu của nút hiện tại không được nénbầu cua, trỏ này sẽ hướng đến một cấu trúc ziplist; ngược lại, nó sẽ hướng đến một cấu trú
  • sz: Biểu thị kích thước tổng thể của ziplist mà con trỏ zl đang chỉ tới (bao gồm zlbytes , zltail , zllen , zlend Bạn có thể tương tác với từng mục dữ liệu. Tuy nhiênbầu cua, cần lưu ý rằng: nếu ziplist đã bị nén, giá trị của sz vẫn sẽ là kích thước của ziplist trước khi bị nén. Điều này có nghĩa là giá trị sz phản ánh kích thước gốc của ziplist, ngay cả khi nó hiện đang ở trạng thái nén.
  • count: đại diện cho số lượng mục dữ liệu có Trường này chỉ có 16 bit. Sau đâybầu cua, chúng ta sẽ cùng tìm hiểu xem liệu 16 bit này có đủ để lưu trữ thông tin cần thiết hay không. Một điều thú vị là ngay cả với dung lượng giới hạn như vậy, ziplist vẫn có thể tối ưu hóa bộ nhớ một cách hiệu quả, đặc biệt khi xử lý các tập dữ liệu nhỏ gọn và đơn giản.
  • Encoding: cho biết liệu ziplist có được nén hay không (và sử dụng thuật toán nén nào). Hiện tạibầu cua, chỉ có hai giá trị có thể: 2 nghĩa là đã được nén (và sử dụng thuật toán nén LZ4). Thuật toán LZ4 là một trong những phương pháp hiệu quả nhất về tốc độ nén dữ liệu, giúp giảm kích thước của ziplist mà vẫn đảm bảo thời gian truy xuất nhanh chóng. Điều này đặc biệt hữu ích khi bạn cần tối ưu hóa bộ nhớ trong các ứng dụng xử lý dữ liệu lớn. LZF Thuật toán nén)mua thẻ trực tuyến, 1 biểu thị không có nén.
  • Trường container: Đây là một trường được sẵnbắn cá săn thưởng, ban đầu được thiết kế để xác định rằng một nút quicklist bên dưới nó sẽ lưu trữ dữ liệu trực tiếp hay sử dụng ziplist để lưu trữ, hoặc thậm chí sử dụng các cấu trúc khác để lưu trữ dữ liệu (vì được dùng như một container dữ liệu, nên được đặt tên là container). Tuy nhiên, trong thực hiện hiện tại, giá trị của trường này luôn cố định ở giá trị 2, có nghĩa là nó sẽ sử dụng ziplist làm container lưu trữ dữ liệu.
  • Khi chúng ta sử dụng các lệnh như lindex để xem một mục dữ liệu đã bị nénbắn cá săn thưởng, hệ thống cần tạm thời giải nén dữ liệu đó. Khi đó, tham số recompress=1 sẽ được đặt làm dấu hiệu, cho phép hệ thống biết rằng dữ liệu này cần được nén lại một lần nữa vào lúc thích hợp trong tương lai. Đây là một cơ chế quan trọng giúp tối ưu hóa việc quản lý tài nguyên và duy trì sự cân bằng giữa hiệu suất và không gian lưu trữ. Bằng cách đánh dấu recompress=1, hệ thống có thể tự động xử lý quá trình nén lại sau khi hoàn thành các tác vụ khác, đảm bảo rằng dữ liệu luôn được sắp xếp gọn gàng mà không ảnh hưởng đến hiệu quả hoạt động chung.
  • Giá trị này chỉ có ý nghĩa đối với chương trình kiểm thử tự động của Redisbắn cá săn thưởng, và chúng ta không cần phải quan tâm đến nó.
  • extra: Trường mở rộng khác. Hiện tại trong việc triển khai của Redis không có gì sử dụng.

Cấu trúc quicklistLZF biểu diễn một ziplist đã được nén. Trong đó:

Cấu trúc thực sự biểu diễn quicklist là struct cùng tên quicklist:

  • head: Con trỏ hướng đến nút đầu tiên (nút đầu tiên bên trái).
  • tail: Con trỏ hướng đến nút cuối cùng (nút đầu tiên bên phải).
  • count: Tổng số lượng mục dữ liệu của tất cả các ziplist.
  • len: Số lượng nú
  • fill: 16bitmua thẻ trực tuyến, thiết lập kích thước ziplist, lưu trữ list-max-ziplist-size Giá trị của tham số.
  • compress: 16bitmua thẻ trực tuyến, thiết lập độ sâu nén nút, lưu trữ list-compress-depth Giá trị của tham số.

Hình ảnh cấu trúc quicklist Redis

Hình ảnh phía trên là một ví dụ về cấu trúc củ Trong hình minh họa nàymua thẻ trực tuyến, các thông số cấu hình liên quan đến kích thước ziplist và độ sâu nén của các nút như sau:

								
									list-max-ziplist-size 3
list-compress-depth 2

								

Những điểm cần chú ý trong ví dụ này là:

  • Tại hai đầubắn cá săn thưởng, mỗi bên có 2 nút màu cam vàng, những nút này chưa bị nén. Pointer dữ liệu zl của chúng trỏ đến thực tế là một ziplist. Trong khi đó, các nút còn lại ở giữa đã bị nén, pointer dữ liệu zl của chúng trỏ đến cấu trúc ziplist đã được nén, cụ thể là mộ Những quicklistLZF này lưu trữ phiên bản nén của dữ liệu gốc trong ziplist, giúp tiết kiệm bộ nhớ mà vẫn đảm bảo hiệu suất đọc ghi.
  • Ở nút đầu tiên bên tráibắn cá săn thưởng, danh sách liên kết đơn ziplist chứa 2 phần tử dữ liệu, trong khi ở nút cuối cùng bên phải, ziplist có 1 phần tử. Những nút còn lại giữa chúng, bao gồm cả các nút được nén bên trong, đều chứa đúng 3 phần tử dữ liệu mỗi cái. Điều này cho thấy rằng đã có nhiều lần thao tác xảy ra tại hai đầu của bảng dữ liệu này. Có thể nói, sự chênh lệch số lượng phần tử giữa các ziplist là dấu hiệu rõ ràng về những hoạt động chỉnh sửa và cập nhật mà bảng đã trải qua trong quá khứ gần đây. push Một quá trình đọc: Quá trình thứ pop Tình trạng sau khi thực hiện thao tác.

Bây giờ chúng ta hãy cùng nhau tính toán sơ bộ xem liệu 16bit trong trường count của cấu trúc quicklistNode có đủ để sử dụng hay không. Trong thực tếmua thẻ trực tuyến, việc phân bổ 16bit cho trường count có thể là một lựa chọn cân nhắc giữa hiệu suất và phạm vi giá trị. Nếu quicklistNode được thiết kế để lưu trữ một số lượng lớn nút, thì 16bit có thể không đủ để biểu diễn số lượng phần tử tối đa mà quicklistNode có thể quản lý. Tuy nhiên, nếu phạm vi ứng dụng chỉ yêu cầu một lượng dữ liệu vừa phải, thì điều này hoàn toàn khả thi. Chúng ta cần xem xét thêm các yếu tố như kích thước bộ nhớ trung bình của mỗi nút, tần suất thay đổi trong danh sách liên kết, và cách mà quicklistNode sẽ tương tác với hệ thống. Điều này giúp đảm bảo rằng việc sử dụng 16bit cho count là hợp lý và tối ưu.

Chúng ta đã biết rằng kích thước ziplist bị giới hạn bởi list-max-ziplist-size Tham số. Có hai trường hợp dựa trên giá trị dương và âm:

  • Khi tham số này có giá trị dươngmua thẻ trực tuyến, nó sẽ chính xác biểu thị số lượng tối đa các mục dữ liệu được chứa trong ziplist mà zl đang trỏ đến trong cấu trú Điều này cho phép quản lý hiệu quả không gian lưu trữ và tối ưu hóa cách dữ liệu được sắp xếp bên trong cấu trúc. list-max-ziplist-size Các tham số được lưu trữ trong trường fill của cấu trú Trường fill có kích thước 16 bitbắn cá săn thưởng, do đó các giá trị mà nó có thể biểu diễn đều có thể được biểu thị bằng 16 bit. Điều này cho phép quicklist tối ưu hóa việc quản lý bộ nhớ và tăng cường hiệu suất khi xử lý dữ liệu với phạm vi giá trị lớn nhưng vẫn giữ được độ gọn nhẹ trong cấu trúc.
  • Khi tham số này có giá trị âmmua thẻ trực tuyến, độ dài tối đa mà ziplist có thể biểu diễn là 64 KB. Mỗi phần tử trong ziplist cần ít nhất 2 byte để lưu trữ: 1 byte cho việc... Xin lỗi, tôi sẽ tiếp tục mà không sử dụng ký tự nào ngoài tiếng Việt: Khi giá trị của tham số này mang dấu âm, chiều dài tối đa mà cấu trúc dữ liệu ziplist có thể lưu trữ là 64 KB. Trong mỗi phần tử của ziplist, ít nhất cần đến 2 byte để biểu diễn: 1 byte cho việc... Tôi sẽ sửa đổi thêm để tạo ra một phiên bản hoàn toàn mới: Khi tham số này ở dạng âm, kích thước tối đa mà chuỗi liên kết nguyên (ziplist) có thể xử lý sẽ đạt mức 64 KB. Mỗi mục trong chuỗi này sẽ cần tối thiểu 2 byte để lưu trữ thông tin: 1 byte đầu tiên dùng để... Tôi sẽ tiếp tục viết tiếp: Khi tham số được thiết lập với giá trị âm, giới hạn tối đa về kích thước mà cấu trúc dữ liệu ziplist có thể hỗ trợ lên tới 64 KB. Trong mỗi phần tử của ziplist, ít nhất cần phải sử dụng 2 byte để lưu giữ thông tin: byte đầu tiên sẽ đảm nhiệm vai trò... Tôi sẽ tiếp tục chỉnh sửa và phát triển thêm: Khi giá trị tham số này mang dấu âm, khả năng tối đa về kích thước mà danh sách liên kết đơn giản (ziplist) có thể chứa được sẽ lên tới 64 KB. Với mỗi phần tử trong danh sách này, cần ít nhất 2 byte để lưu trữ: byte thứ nhất thường được sử dụng để... prevrawlen bắn cá săn thưởng, trường hợp 1 byte của data len Trường và data Được hợp nhất; xem thêm Bài trước Vì vậybắn cá săn thưởng, số lượng mục dữ liệu trong ziplist sẽ không vượt quá 32K, và việc sử dụng 16 bit để biểu diễn là đủ. Ngoài ra, việc giới hạn này cũng giúp tối ưu hóa không gian lưu trữ, đảm bảo hiệu suất cao hơn cho các hoạt động xử lý dữ liệu liên quan đến ziplist.

Trên thực tếmua thẻ trực tuyến, trong việc triển khai hiện tại của quicklist, kích thước của ziplist còn bị giới hạn bởi các yếu tố khác và sẽ không bao giờ đạt đến giá trị tối đa mà chúng ta đang phân tích ở đây. Điều này có nghĩa là dù bạn có thêm nhiều phần tử vào ziplist, nó vẫn sẽ tự động điều chỉnh để tuân theo những giới hạn riêng biệt, đảm bảo hiệu suất và khả năng hoạt động ổn định.

Bây giờ hãy chuyển sang giai đoạn phân tích mã nguồn.

Tạo quicklist

Khi chúng ta sử dụng lpush Hai hàm callback này. Thực tếmua thẻ trực tuyến, cách mô tả này vẫn còn khá rpush Lệnh chèn dữ liệu lần đầu tiên vào một list chưa tồn tạibắn cá săn thưởng, Redis sẽ gọi quicklistCreate Giao diện để tạo một quicklist rỗng.

								
									
										quicklist
									 *
									quicklistCreate
									(
									void
									)
									 {
									
    struct
									 quicklist
									 *
									quicklist
									;
									

    quicklist
									 =
									 zmalloc
									(
									sizeof
									(
									*
									quicklist
									));
									
    quicklist
									->
									head
									 =
									 quicklist
									->
									tail
									 =
									 NULL
									;
									
    quicklist
									->
									len
									 =
									 0
									;
									
    quicklist
									->
									count
									 =
									 0
									;
									
    quicklist
									->
									compress
									 =
									 0
									;
									
    quicklist
									->
									fill
									 =
									 -
									2
									;
									
    return
									 quicklist
									;
									
}
									

								

Trong nhiều cuốn sách hướng dẫn về cấu trúc dữ liệumua thẻ trực tuyến, khi triển khai danh sách liên kết hai chiều, người ta thường thêm vào một nút đầu trống để làm cho việc chèn và xóa trở nên thuận tiện hơn. Điều này xuất phát từ thực tế rằng việc có một nút đầu đặc biệt giúp tránh các trường hợp viền bờ phức tạp khi thao tác với danh sách. Khi có một nút đầu trống như vậy, các hoạt động trên danh sách liên kết sẽ được quản lý dễ dàng hơn mà không cần phải kiểm tra xem phần tử hiện tại có phải là phần tử đầu tiên hay không. Điều này không chỉ giúp tiết kiệm thời gian lập trình mà còn giảm thiểu lỗi tiềm ẩn trong quá trình xử lý. Ngoài ra, cách thiết kế này cũng giúp mã nguồn trở nên sạch hơn và dễ bảo trì hơn vì mọi thao tác đều được thống nhất theo cùng một quy tắc. Từ quan điểm của lập trình viên, việc thêm nút đầu trống không chỉ là một giải pháp kỹ thuật mà còn là một chiến lược giúp tối ưu hóa hiệu suất và độ tin cậy của chương trình. Với sự hỗ trợ của nút này, việc xử lý danh sách liên kết trở nên đơn giản hơn rất nhiều, đặc biệt khi cần thực hiện các thao tác phức tạp như chèn hoặc xoá ở bất kỳ vị trí nào trong danh sách. quicklistCreate Mã nguồn cho thấybầu cua, quicklist là một danh sách liên kết hai chiều không có nút đầu thừa (tất cả đều được khởi tạo thành NULL). head Một quá trình đọc: Quá trình thứ tail Thao tác push quicklist

Thao tác push quicklist được thực hiện bằng cách gọi

Dù chèn dữ liệu vào đầu hay cuốimua thẻ trực tuyến, đều bao gồm hai trường hợp: quicklistPush Nếu kích thước ziplist trên nút đầu (hoặc cuối) không vượt quá giới hạn (tức là

								
									
										void
									 quicklistPush
									(
									quicklist
									 *
									quicklist
									,
									 void
									 *
									value
									,
									 const
									 size_t
									 sz
									,
									
                   int
									 where
									)
									 {
									
    if
									 (
									where
									 ==
									 QUICKLIST_HEAD
									)
									 {
									
        quicklistPushHead
									(
									quicklist
									,
									 value
									,
									 sz
									);
									
    }
									 else
									 if
									 (
									where
									 ==
									 QUICKLIST_TAIL
									)
									 {
									
        quicklistPushTail
									(
									quicklist
									,
									 value
									,
									 sz
									);
									
    }
									
}
									

/* Add new entry to head node of quicklist.
 *
 * Returns 0 if used existing head.
 * Returns 1 if new head created. */
									
int
									 quicklistPushHead
									(
									quicklist
									 *
									quicklist
									,
									 void
									 *
									value
									,
									 size_t
									 sz
									)
									 {
									
    quicklistNode
									 *
									orig_head
									 =
									 quicklist
									->
									head
									;
									
    if
									 (
									likely
									(
									
            _quicklistNodeAllowInsert
									(
									quicklist
									->
									head
									,
									 quicklist
									->
									fill
									,
									 sz
									)))
									 {
									
        quicklist
									->
									head
									->
									zl
									 =
									
            ziplistPush
									(
									quicklist
									->
									head
									->
									zl
									,
									 value
									,
									 sz
									,
									 ZIPLIST_HEAD
									);
									
        quicklistNodeUpdateSz
									(
									quicklist
									->
									head
									);
									
    }
									 else
									 {
									
        quicklistNode
									 *
									node
									 =
									 quicklistCreateNode
									();
									
        node
									->
									zl
									 =
									 ziplistPush
									(
									ziplistNew
									(),
									 value
									,
									 sz
									,
									 ZIPLIST_HEAD
									);
									

        quicklistNodeUpdateSz
									(
									node
									);
									
        _quicklistInsertNodeBefore
									(
									quicklist
									,
									 quicklist
									->
									head
									,
									 node
									);
									
    }
									
    quicklist
									->
									count
									++
									;
									
    quicklist
									->
									head
									->
									count
									++
									;
									
    return
									 (
									orig_head
									 !=
									 quicklist
									->
									head
									);
									
}
									

/* Add new entry to tail node of quicklist.
 *
 * Returns 0 if used existing tail.
 * Returns 1 if new tail created. */
									
int
									 quicklistPushTail
									(
									quicklist
									 *
									quicklist
									,
									 void
									 *
									value
									,
									 size_t
									 sz
									)
									 {
									
    quicklistNode
									 *
									orig_tail
									 =
									 quicklist
									->
									tail
									;
									
    if
									 (
									likely
									(
									
            _quicklistNodeAllowInsert
									(
									quicklist
									->
									tail
									,
									 quicklist
									->
									fill
									,
									 sz
									)))
									 {
									
        quicklist
									->
									tail
									->
									zl
									 =
									
            ziplistPush
									(
									quicklist
									->
									tail
									->
									zl
									,
									 value
									,
									 sz
									,
									 ZIPLIST_TAIL
									);
									
        quicklistNodeUpdateSz
									(
									quicklist
									->
									tail
									);
									
    }
									 else
									 {
									
        quicklistNode
									 *
									node
									 =
									 quicklistCreateNode
									();
									
        node
									->
									zl
									 =
									 ziplistPush
									(
									ziplistNew
									(),
									 value
									,
									 sz
									,
									 ZIPLIST_TAIL
									);
									

        quicklistNodeUpdateSz
									(
									node
									);
									
        _quicklistInsertNodeAfter
									(
									quicklist
									,
									 quicklist
									->
									tail
									,
									 node
									);
									
    }
									
    quicklist
									->
									count
									++
									;
									
    quicklist
									->
									tail
									->
									count
									++
									;
									
    return
									 (
									orig_tail
									 !=
									 quicklist
									->
									tail
									);
									
}
									

								

Trả về 1)bầu cua, thì dữ liệu mới sẽ được chèn trực tiếp vào ziplist (gọi

  • Trong việc thực hiệnbắn cá săn thưởng, còn tùy thuộc vào _quicklistNodeAllowInsert Cấu hình để nén các nút bên trong. Việc triển khai khá phức tạpmua thẻ trực tuyến, chúng tôi sẽ không thảo luận chi tiết ở đây. ziplistPush )。
  • Nếu danh sách liên kết ziplist tại nút đầu (hoặc nút cuối) quá lớnbầu cua, thì sẽ tạo ra một nút quicklist mới (cùng với việc tạo mới một ziplist tương ứng), sau đó chèn nút này vào danh sách liên kết hai chiều quicklist (bằng cách gọi hàm). _quicklistInsertNodeAfter )。

Nên là 43; ngược lạibắn cá săn thưởng, nếu chúng ta cho rằng _quicklistInsertNodeAfter Các thao tác khác của quicklist list-compress-depth Thao tác pop quicklist được thực hiện bằng cách gọi

Quicklist không chỉ thực hiện chèn từ đầu hoặc cuốibầu cua, mà còn thực hiện chèn từ bất kỳ vị trí nào được chỉ định.

Các thao tác của quicklist khá nhiều và các chi tiết thực hiện cũng tương đối phức tạpmua thẻ trực tuyến, vì vậy ở đây chúng ta sẽ không đi sâu phân tích mã nguồn nữa. Thay vào đó, chúng tôi sẽ giới thiệu sơ lược về một số thao tác quan trọng.

Được sử dụng để thiết lập tham số cấu hình kích thước ziplist ( quicklistPopCustom Nếu kích thước ziplist trên nút đầu (hoặc cuối) không vượt quá giới hạn (tức là quicklistPopCustom Quá trình thực hiện cơ bản là ngược lại so vớ Đầu tiênbầu cua, bạn cần xóa mục dữ liệu tương ứng từ ziplist của nút đầu hoặc nút cuối. Nếu sau khi xóa mà ziplist trở nên trống rỗng, thì nút đầu hoặc nút cuối đó cũng cần bị xóa đi. Sau khi xóa, có thể còn phát sinh vấn đề liên quan đến việc giải nén các nút bên trong.

) và tham số cấu hình độ sâu nén nút ( quicklistInsertAfter Một quá trình đọc: Quá trình thứ quicklistInsertBefore Bạn có thể chèn các mục dữ liệu ngay sau hoặc ngay trước vị trí được chỉ định. Hoạt động chèn dữ liệu tại bất kỳ vị trí nào được chỉ định này khá phức tạpmua thẻ trực tuyến, với nhiều nhánh logic khác nhau cần được xử lý. Trong thực tế, việc thực hiện thao tác này yêu cầu lập trình viên phải cẩn trọng và chi tiết để đảm bảo rằng mọi trường hợp đặc biệt đều được tính đến. Ví dụ, nếu vị trí cần chèn nằm ở đầu danh sách, giữa danh sách hay gần cuối danh sách sẽ đòi hỏi cách tiếp cận hoàn toàn khác nhau. Hơn nữa, khi danh sách đã đạt đến giới hạn dung lượng, hệ thống cần phải kiểm tra điều kiện mở rộng bộ nhớ hoặc thông báo lỗi cho người dùng. Điều này không chỉ liên quan đến việc di chuyển các phần tử trong cấu trúc dữ liệu mà còn cần quản lý hiệu quả tài nguyên hệ thống, giúp duy trì sự ổn định và hiệu suất tối ưu của ứng dụng.

  • Khi kích thước của ziplist tại vị trí chèn không vượt quá giới hạnmua thẻ trực tuyến, bạn chỉ cần chèn trực tiếp vào bên trong ziplist là được;
  • Khi kích thước của ziplist tại vị trí chèn vượt quá giới hạnbầu cua, nhưng vị trí chèn nằm ở hai đầu của ziplist và kích thước của ziplist trong các nút quicklist liền kề không vượt quá giới hạn, thì thay vì tạo thêm một phần tử mới, dữ liệu sẽ được chèn vào ziplist của nút quicklist liền kề đó. Điều này giúp tối ưu hóa việc quản lý bộ nhớ và duy trì hiệu suất của cấu trúc dữ liệu.
  • Khi kích thước của ziplist tại vị trí chèn vượt quá giới hạnmua thẻ trực tuyến, nhưng vị trí chèn nằm ở hai đầu của ziplist và cả ziplist của các nút quicklist liền kề cũng vượt quá giới hạn, thì lúc này cần tạo một nút quicklist mới để chèn vào. Điều này đảm bảo rằng cấu trúc dữ liệu vẫn hoạt động ổn định và hiệu quả trong việc quản lý bộ nhớ, đồng thời duy trì sự cân bằng giữa các nút quicklist trong hệ thống.
  • Trong trường hợp ziplist có kích thước vượt quá giới hạn mà việc chèn dữ liệu xảy ra ở giữa ziplist (trường hợp chủ yếu là chèn giữa)bầu cua, cần phải tách ziplist hiện tại thành hai phần tử riêng biệt. Sau đó, dữ liệu mới sẽ được chèn vào một trong hai phần tử này. Quy trình này giúp duy trì hiệu quả và tính toàn vẹn của cấu trúc dữ liệu khi kích thước của ziplist bị vượt quá ngưỡng cho phép.

quicklistSetOptions ) và tham số cấu hình độ sâu nén nút ( list-max-ziplist-size list-compress-depth Mã code khá đơn giảnmua thẻ trực tuyến, chỉ cần gán các giá trị tương ứng lần lượt cho trường fill và compress trong cấu trú Bên cạnh đó, đoạn mã này cũng đảm bảo tính linh hoạt khi có thể điều chỉnh các thông số liên quan đến việc nén dữ liệu một cách dễ dàng mà không làm thay đổi toàn bộ cấu trúc ban đầu.


Bài viết tiếp theo của chúng tôi sẽ đề cập đến cấu trúc dữ liệu skiplist và cách nó hỗ trợ cho kiểu dữ liệu Redis là sorted setmua thẻ trực tuyến, hãy cùng đón chờ nhé.


Bài viết gốcbắn cá săn thưởng, xin vui lòng trích dẫn 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: /qfwbpqpb.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: Blog Programmer Choice
Bài sau: [Truyện ngắn não bộ của lập trình viên] Mô tả ký tự tại tận cùng vũ trụ

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