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 (5) —— quicklist


Phân tích cấu trúc dữ liệu nội bộ của Redis Phân tích cấu trúc dữ liệu nội bộ của Redis Trong bài viết nàysv 88, chúng ta sẽ đi sâu vào phân tích một trong những cấu trúc dữ liệu nội bộ quan trọng của Redis – Quicklist là thành phần cốt lõi mà Redis sử dụng để hỗ trợ kiểu dữ liệu list mà nó cung cấp ra bên ngoài. Đây là phần thứ năm trong loạt bài viết của chúng tôi về Redis, và nó đóng vai trò đặc biệt quan trọng trong việc hiểu cách hoạt động bên trong của Redis khi xử lý các danh sách.

Trong quá trình thảo luậnsv 88, chúng ta sẽ cùng tìm hiểu thêm về hai cấu hình Redis được đề cập rõ ràng trong phần CẤU HÌNH NÂNG CAO (ADVANCED CONFIG) của tậ conf.

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

								

Trong quá trình thảo luậnđá gà trực tiếp app, 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 này về quicklist dựa trên nhánh nguồn code của Redis 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ênsv 88, thường được sử dụng như một hàng đợi (queue). Một số thao tác mà nó hỗ trợ, chẳng hạn như: - Thêm phần tử vào cuối danh sách (LPUSH), giống như thêm một công việc vào cuối hàng đợi. - Lấy phần tử từ đầu danh sách (LPOP), tương đương với lấy ra công việc đầu tiên trong hàng đợi. - Đọc nhanh các phần tử mà không làm thay đổi danh sách (LRANGE), giúp kiểm tra trạng thái hiện tại của hàng đợi. - Kiểm tra xem danh sách có rỗng hay không (LLEN), cho phép xác định khi nào hàng đợi đã hoàn toàn được xử lý. Các tính năng này đã giúp Redis trở thành một lựa chọn linh hoạt và mạnh mẽ cho các ứng dụng cần quản lý dữ liệu theo mô hình hàng đợi.

  • 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).

Các hoạt động này đều có độ phức tạp thời gian O(1).

Tất nhiênkeo 88, list cũng hỗ trợ thao tác truy cập tại bất kỳ vị trí nào giữa, ví dụ: lindex linsert đá gà trực tiếp app, nhưng chúng cần duyệt qua toàn bộ list, do đó độ phức tạp thời gian cao hơn, là O(N).

Tóm lạisv 88, một list có những đặc điểm như sau: đây là một danh sách có thể duy trì thứ tự của các phần tử (thứ tự được xác định bởi vị trí chèn), dễ dàng thêm hoặc xóa dữ liệu ở hai đầu danh sách, nhưng việc truy xuất ở giữa danh sách sẽ có độ phức tạp thời gian O(n). Điều này chẳng phải cũng là những gì mà một danh sách liên kết đôi có sao? Một danh sách liên kết đôi cho phép chúng ta thao tác linh hoạt ở cả hai đầu, và trong khi đó vẫn giữ được tính năng quản lý dữ liệu theo thứ tự. Điều này làm cho nó trở thành một lựa chọn rất hữu ích trong nhiều trường hợp lập trình.

Thực hiện nội bộ của list chính là một danh sách liên kết hai chiều. Trong phần chú thích đầ ckeo 88, 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ềusv 88, và là một danh sách ziplist hai chiều.

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

Chúng ta đều hiểu rằngsv 88, danh sách liên kết hai chiều được tạo thành từ nhiều nút (Node). Điều này có nghĩa là mỗi nút trong quicklist đều là một ziplist. Ziplist đã được đề cập trước đây, và nó là một cấu trúc dữ liệu đặc biệt được sử dụng để lưu trữ các phần tử theo thứ tự tuần tự trong bộ nhớ. Với khả năng tối ưu hóa không gian lưu trữ và hiệu suất truy xuất, ziplist đóng vai trò quan trọng trong việc quản lý dữ liệu bên trong quicklist, cho phép hệ thống thực hiện các thao tác như chèn, xóa hoặc truy vấn một cách linh hoạt và hiệu quả. Bài trước Đã được giới thiệu trước đó.

Ziplist không chỉ là một danh sách có khả năng duy trì thứ tự các phần tử (theo thứ tự chúng được thêm vào)keo 88, mà còn là một danh sách tối ưu về mặt bộ nhớ (các phần tử nằm liền kề nhau trong bộ nhớ). Ví dụ, một quicklist gồm 3 nút, và mỗi nút ziplist lại chứa 4 phần tử, thì về mặt biểu hiện bên ngoài, danh sách này sẽ chứa tổng cộng 12 phần tử. Điều thú vị là khi bạn thao tác với quicklist này, nó sẽ hoạt động như thể tất cả các phần tử đã được nối thành một chuỗi duy nhất, tạo ra hiệu suất tốt hơn trong việc quản lý và truy xuất dữ liệu.

Tại sao cấu trúc của quicklist lại được thiết kế theo cách này? Nếu tóm tắt lạikeo 88, đó có lẽ là sự đánh đổi giữa không gian và thời gian: Trong thế giới lập trình, mọi thứ đều cần cân nhắc kỹ lưỡng giữa hiệu quả về bộ nhớ (không gian) và tốc độ xử lý (thời gian). Với quicklist, việc tối ưu hóa này giúp hệ thống có thể truy xuất dữ liệu nhanh chóng mà vẫn đảm bảo không tiêu tốn quá nhiều tài nguyên. Khi bạn duyệt qua danh sách, các phần tử được sắp xếp một cách thông minh để giảm thiểu thời gian tìm kiếm và tăng khả năng phản hồi tức thì cho người dùng. Với sự kết hợp khéo léo giữa hai yếu tố này, quicklist trở thành công cụ hữu ích trong việc tối ưu hóa hiệu suất tổng thể của ứng dụng hoặc hệ thống mà nó phục vụ.

  • 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áchkeo 88, nhưng nó đòi hỏi một lượng bộ nhớ lớn hơn. Trước tiên, ngoài việc lưu trữ dữ liệu, mỗi nút trong danh sách này còn cần phải lưu thêm hai con trỏ tham chiếu; tiếp theo, các nút của danh sách liên kết hai chiều được phân bổ thành từng khối bộ nhớ riêng biệt, 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 có thể 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 là một khối bộ nhớ liên tụcđá gà trực tiếp app, nó có hiệu quả lưu trữ rất cao. Tuy nhiên, điều này lại không thuận lợi cho các thao tác sửa đổi, vì mỗi khi dữ liệu thay đổi, việc cấp phát lại bộ nhớ (realloc) sẽ được kích hoạt. Đặc biệt, khi ziplist có chiều dài lớn, một lần realloc có thể dẫn đến việc sao chép hàng loạt dữ liệu, làm giảm hiệu suất đáng kể. Khi ziplist mở rộng, quá trình realloc buộc phải tìm kiếm vùng nhớ mới đủ lớn để chứa toàn bộ dữ liệu hiện tại, sau đó di chuyển tất cả các phần tử từ vị trí cũ sang vị trí mới. Điều này không chỉ tốn thời gian mà còn làm gián đoạn hoạt động của ứng dụng trong một khoảng thời gian nhất định. Vì vậy, khi sử dụng ziplist, cần cân nhắc kỹ lưỡng về kích thước và tần suất cập nhật dữ liệu để tránh những vấn đề này.

Do đókeo 88, 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ênđá gà trực tiếp app, điều này cũng đặt ra một vấn đề mới: Liệu số lượng phần tử trong ziplist của một nút quicklist nên là bao nhiêu thì hợp lý? Ví dụ, khi lưu trữ 12 mục dữ liệu, ta có thể chọn cấu hình quicklist với 3 nút, mỗi nút chứa 4 phần tử ziplist; hoặc cũng có thể chia thành 6 nút, mỗi nút chỉ chứa 2 phần tử ziplist. Vậy, cách nào sẽ tối ưu hơn về hiệu suất và không gian sử dụng? Đây thực sự là một thách thức cần được cân nhắc kỹ lưỡng.

Đây lại là một bài toán khó cần tìm điểm cân bằng. Chúng ta chỉ phân tích một chút về hiệu quả lưu trữ:

  • Khi mỗi nút quicklist có ziplist càng ngắnđá gà trực tiếp app, mức độ phân mảnh bộ nhớ sẽ tăng lên. Khi các mảnh phân mảnh tích lũy ngày càng nhiều trong bộ nhớ, chúng có thể tạo ra rất nhiều mảnh nhỏ không thể được sử dụng hiệu quả, từ đó làm giảm hiệu suất lưu trữ. Tuyệt đỉnh của tình trạng này là khi mỗi nút quicklist chỉ chứa một mục dữ liệu duy nhất trong ziplist, điều này thực chất biến nó thành một danh sách liên kết hai chiều thông thường.
  • Mỗi nút quicklist với ziplist dài hơn sẽ khiến việc cấp phát vùng nhớ liên tục lớn cho ziplist trở nên khó khăn hơn. Có thể xảy ra tình huống mà trong bộ nhớ có nhiều khoảng trống nhỏ rải rác (tổng cộng chúng khá lớn)đá gà trực tiếp app, nhưng không tìm được một vùng đủ lớn để gá Điều này cũng sẽ làm giảm hiệu quả lưu trữ. Tuyệt vọng nhất là trường hợp quicklist chỉ có một nút duy nhất, tất cả các mục dữ liệu đều được phân bổ trong ziplist của nút đó. Thực tế, điều này đã trở thành một ziplist thuần túy.

Rõ ràngsv 88, một nút quicklist cần duy trì độ dài của ziplist ở mức hợp lý. Nhưng chính xác bao nhiêu là hợp lý thì có thể phụ thuộc vào ngữ cảnh sử dụng cụ thể. Thực tế, Redis cung cấp một tham số cấu hình để giúp người dùng điều chỉnh điều này. Tham số này được gọi là **list-compress-depth** trong tệp cấu hình Redis. Nó cho phép bạn thiết lập số lượng nút quicklist ở đầu và cuối danh sách mà không áp dụng nén. Điều này giúp tối ưu hóa hiệu suất khi truy xuất dữ liệu ở các vị trí gần phần đầu hoặc phần cuối của danh sách. Ví dụ, nếu bạn thường xuyên thực hiện các thao tác thêm/xóa tại hai đầu danh sách, việc tăng giá trị của tham số này sẽ giảm thiểu thời gian giải nén và cải thiện tốc độ xử lý. Tuy nhiên, hãy cân nhắc kỹ lưỡng khi đặt giá trị cho tham số này. Nếu giá trị quá lớn, bộ nhớ có thể bị lãng phí do nhiều nút quicklist chưa được nén. Ngược lại, nếu giá trị quá nhỏ, hiệu quả nén có thể không cao, dẫn đến sự gia tăng đáng kể kích thước bộ nhớ sử dụng. Vì vậy, tùy thuộc vào nhu cầu ứng dụng và đặc thù của dữ liệu, bạn nên thử nghiệm với các giá trị khác nhau để tìm ra sự cân bằng tốt nhất giữa hiệu suất và tài nguyên hệ thống. list-max-ziplist-size sv 88, để người dùng có thể đ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ươngsv 88, nó sẽ giới hạn độ dài của ziplist trên mỗi nút quicklist 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, có nghĩa là mỗi nút quicklist chỉ có thể chứa tối đa 5 mục trong ziplist của nó. Ngoài ra, việc điều chỉnh tham số này giúp tối ưu hóa bộ nhớ và cải thiện hiệu suất xử lý dữ liệu. Khi giá trị được đặt thấp, hệ thống sẽ sử dụng ít bộ nhớ hơn nhưng có thể dẫn đến tăng số lượng nút cần quản lý. Ngược lại, nếu giá trị quá cao, mặc dù giảm được số lượng nút nhưng có thể làm tăng thời gian truy xuất dữ liệu. Vì vậy, việc chọn giá trị phù hợp cho tham số này là rất quan trọng trong việc duy trì hiệu quả hoạt động tổng thể của hệ thống.

Khi đặt giá trị âmsv 88, điều này có nghĩa là giới hạn độ dài của ziplist trong mỗi nút quicklist dựa trên số lượng 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ủa từng giá trị như sau:

  • -5: Kích thước của mỗi nút quicklist trên ziplist không được vượt quá 64 KB. (Ghi chú: 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.
  • -2: Kích thước của mỗi nút quicklist trên ziplist không được vượt quá 8 Kb. (-2 là giá trị mặc định do Redis đưa ra)
  • -1: Kích thước ziplist trên mỗi nút quicklist không vượt quá 4 Kb.

Ngoài rađá gà trực tiếp app, mục tiêu của list không chỉ đơn thuần là để lưu trữ các dữ liệu mà còn hướng đến việc quản lý danh sách dữ liệu dài một cách hiệu quả. Ví dụ như trong hướng dẫn trên trang web chính thức của Redis, họ đã đưa ra một bài học cụ thể về cách sử dụng list để lưu trữ và xử lý khối lượng thông tin lớn một cách nhanh chóng và gọn gàng. Hãy tưởng tượng bạn đang điều hành một nền tảng truyền thông xã hội với hàng triệu bài đăng mỗi ngày, list sẽ trở thành công cụ hoàn hảo giúp bạn tổ chức tất cả dữ liệu này theo thứ tự thời gian thực, cho phép bạn truy xuất hoặc thêm bớt dữ liệu một cách dễ dàng mà không gặp bất kỳ vấn đề nào liên quan đến hiệu suất. Writing a simple Twitter clone with PHP and Redis keo 88, tức là sử dụng list để lưu trữ dữ liệu timeline giố

Khi danh sách trở nên dàikeo 88, 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 các phần ở giữa. Việc truy cập vào giữa danh sách không chỉ ít tần suất hơn mà còn có thể làm giảm hiệu suất tổng thể. Nếu ứng dụng của bạn gặp phải đặc điểm này, thì danh sách (list) sẽ cung cấp một tùy chọn để nén các nút dữ liệu ở giữa, từ đó giúp tiết kiệm thêm không gian bộ nhớ. Trong Redis, có một số tham số cấu hình có thể điều chỉnh để tối ưu hóa hành vi này. Điều thú vị là việc nén dữ liệu ở giữa danh sách không chỉ ảnh hưởng đến hiệu suất bộ nhớ mà còn có thể cải thiện tốc độ phản hồi trong trường hợp bạn cần xử lý một lượng lớn dữ liệu. Tuy nhiên, để đạt được hiệu quả tối đa, người dùng cần hiểu rõ cấu trúc dữ liệu mình đang làm việc và lựa chọn cách tối ưu phù hợp nhất. Tham số cấu hình của Redis cho phép bạn linh hoạt điều chỉnh mức độ nén dựa trên nhu cầu cụ thể của ứng dụng. Điều này thực sự hữu ích khi bạn phải đối mặt với những bài toán liên quan đến lưu trữ và xử lý dữ liệu lớn trong hệ thống Redis. list-compress-depth Là để hoàn thành việc thiết lập này.

								
									list-compress-depth 0

								

Tham số này cho biết số lượng nút ở hai đầu của quicklist mà không bị nén. Lưu ý: 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 quicklistđá gà trực tiếp app, 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 trên nút đó sẽ bị nén hoàn toàn. Trong ngữ cảnh hoạt động của quicklist, việc duy trì một số lượng nhất định các nút không bị nén ở hai đầu có thể giúp tối ưu hóa hiệu suất truy xuất dữ liệu. Điều này đặc biệt quan trọng khi thực hiện các thao tác đọc hoặc ghi dữ liệu liên tục mà không làm gián đoạn chuỗi xử lý thông qua các nút đã được nén trước đó.

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

  • 0: Là giá trị đặc biệtkeo 88, biểu thị không nén. Đây là giá trị mặc định của Redis.
  • 1: Biểu thị 1 nút không nén ở mỗi đầu quicklistsv 88, các nút giữa bị nén.
  • 2: Biểu thị 2 nút không nén ở mỗi đầu quicklistkeo 88, các nút giữa bị nén.
  • 3: Biểu thị 3 nút không nén ở mỗi đầu quicklistđá gà trực tiếp app, các nút giữa bị nén.
  • Theo thứ tự tiếp tục...

Do 0 là một giá trị đặc biệtsv 88, 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 quicklist luôn không bị nén lại. Điều này giúp việc truy xuất dữ liệu ở hai đầu danh sách trở nên nhanh chóng và hiệu quả hơn.

Thuật toán nén bên trong các nút quicklist của 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 quicklistkeo 88, với các trường dữ liệu bên dưới có ý nghĩa 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.
  • Trong ngữ cảnh nàyđá gà trực tiếp app, con trỏ dữ liệu đóng vai trò như một tham chiếu. Nếu dữ liệu của nút hiện tại chưa được nén, nó sẽ ánh xạ đến một cấu trúc ziplist; ngược lại, nếu dữ liệu đã được nén, con trỏ này sẽ liên kết với một cấu trúc quicklistLZF, cho phép lưu trữ dữ liệu đã được tối ưu hóa kích thước.
  • sz: Thể hiện kích thước tổng thể của ziplist được chỉ bởi con trỏ zl (bao gồm zlbytes , zltail , zllen , zlend Khi làm việc với từng mục dữ liệuđá gà trực tiếp app, cần lưu ý rằng: nếu ziplist đã được nén, giá trị của "sz" vẫn sẽ là kích thước ban đầu của ziplist trước khi bị nén. Điều này có nghĩa là ngay cả khi dữ liệu đã trải qua quá trình nén, thông số "sz" vẫn phản ánh kích thước gốc của nó, không phải kích thước sau khi nén.
  • Số lượng: đại diện cho số lượng mục dữ liệ Trường này chỉ có 16 bit. Sau đósv 88, chúng ta sẽ cùng nhau tính xem liệu 16 bit này có đủ để sử dụng hay không. Ngoài ra, cần lưu ý rằng việc quản lý số lượng phần tử trong ziplist bằng cách sử dụng một trường 16 bit này mang lại sự tối ưu hóa đáng kể về mặt bộ nhớ và hiệu suất khi xử lý khối lượng lớn dữ liệu.
  • Quy định mã hóa: cho biết liệu ziplist có được nén (và sử dụng thuật toán nén nào) hay không. Hiện tại chỉ có hai giá trị khả dụng: 2 có nghĩa là nó đã được nén và sử dụng thuật toán nén cụ thể như... LZF Thuật toán nén)keo 88, 1 biểu thị không nén.
  • container: Đây là một trường dự phòng được thiết kế ban đầu để xác định xem một nút quicklist phía dưới sẽ lưu trữ dữ liệu trực tiếp hay sử dụng ziplist để lưu trữđá gà trực tiếp app, hoặc thậm chí sử dụng một cấu trúc khác để lưu trữ dữ liệu (vì nó được dùng như một "thùng chứa" dữ liệu nên được gọi là container). Tuy nhiên, trong triển khai hiện tại, giá trị của trường này luôn cố định là 2, biểu thị việc sử dụng ziplist làm thùng chứa dữ liệu. Điều này cho thấy rằng mặc dù ý tưởng ban đầu rất linh hoạt, nhưng hiện tại nó đã bị hạn chế ở một giải pháp cụ thể là ziplist, điều này có thể là do sự tối ưu hóa hoặc đơn giản hóa trong quá trình thực hiện hệ thống.
  • khi chúng ta sử dụng các lệnh như lindex để xem xét một mục dữ liệu đã được nénkeo 88, hệ thống cần tạm thời giải nén dữ liệu để có thể truy cập vào nội dung bên trong. Để đánh dấu rằng dữ liệu này cần được nén lại sau khi đã hoàn thành việc kiểm tra hoặc thao tác cần thiết, ta sẽ đặt giá trị recompress = 1. Điều này cho phép hệ thống ghi nhớ rằng dữ liệu đó sau này sẽ cần được nén trở lại nhằm tiết kiệm không gian lưu trữ, đảm bảo hiệu quả hoạt động về lâu dài. Khi cơ hội thích hợp xuất hiện, chẳng hạn như khi hệ thống rảnh rỗi hoặc không có yêu cầu cao về hiệu suất, dữ liệu sẽ tự động được nén lại dựa trên thông tin đánh dấu này, giúp duy trì trạng thái tối ưu cho kho dữ liệu tổng thể.
  • Giá trị này chỉ có ý nghĩa đối với chương trình kiểm thử tự động của Redissv 88, vì vậy 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 triển khai của Redis không dùng đến.

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

  • sz: Thể hiện kích thước ziplist sau khi nén.
  • compressed: Mảng linh hoạt ( flexible array member )keo 88, chứa mảng byte của ziplist đã nén.

Cấu trúc thực sự đại diện cho 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: 16bitsv 88, cài đặt kích thước ziplist, lưu trữ list-max-ziplist-size Giá trị tham số.
  • compress: 16bitkeo 88, cài đặt độ sâu nén nút, lưu trữ list-compress-depth Giá trị tham số.

Sơ đồ cấu trúc quicklist Redis

Hình ảnh phía trên là một ví dụ về cấu trúc củ Đối với ví dụ trong hìnhkeo 88, các thông số được thiết lập cho ziplist và độ sâu nén của 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 mỗi đầu của dãy có 2 nút màu cam vàngsv 88, những nút này chưa bị nén. Pointer dữ liệu zl của chúng sẽ trỏ đến thực thể ziplist thật sự. Trong khi đó, các nút còn lại ở giữa đã bị nén, pointer dữ liệu zl của chúng sẽ trỏ đến cấu trúc ziplist đã được nén, cụ thể là một cấu trú
  • Ở nút đầu tiên bên tráikeo 88, danh sách liên kết ziplist chứa 2 mục dữ liệu, trong khi ở nút cuối cùng bên phải, ziplist có 1 mục dữ liệu. Những nút còn lại giữa chúng đều chứa các ziplist với 3 mục dữ liệu mỗi cái (bao gồm cả những phần được nén bên trong nút). Điều này cho thấy rằng đã có nhiều lần thao tác thực hiện tại hai đầu của bảng dữ liệu này. Có thể nói, sự khác biệt về số lượng mục trong các ziplist ở các vị trí khác nhau phản ánh cách mà dữ liệu đã bị phân bổ và điều chỉnh qua nhiều bước xử lý trước đó. push pop Một trạng thái sau khi thực hiện.

Bây giờ chúng ta hãy cùng nhau tính toán sơ bộ xem việc sử dụng 16 bit cho trường count trong cấu trúc quicklistNode có đủ không. Trong lập trìnhsv 88, việc chọn kích thước phù hợp cho các trường dữ liệu rất quan trọng. QuicklistNode là một phần quan trọng của cơ chế quản lý danh sách liên kết, nơi mà count sẽ dùng để đếm số lượng phần tử trong node đó. Nếu count chỉ được cấp phát 16 bit (tức là tối đa 2^16 giá trị), thì nó có thể chứa tối đa 65,535 giá trị. Chúng ta cần xem xét quy mô dữ liệu thực tế mà quicklistNode đang xử lý. Ví dụ như nếu mỗi node lưu trữ một chuỗi ký tự hoặc các cấu trúc phức tạp, có thể cần nhiều hơn 16 bit để biểu diễn số lượng phần tử. Tuy nhiên, nếu dữ liệu đơn giản và có giới hạn trong khoảng nhỏ, 16 bit có thể hoàn toàn ổn thỏa. Tóm lại, việc xác định xem liệu 16 bit có đủ hay không phụ thuộc vào yêu cầu cụ thể của ứng dụng và cách mà quicklistNode được triển khai. Chúng ta cần đánh giá kỹ lưỡng trước khi đưa ra quyết định cuối cùng.

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ươngkeo 88, nó chính xác biểu thị số lượng tối đa các mục dữ liệu được lưu trữ 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 nhớ và tăng tốc độ xử lý dữ liệu. list-max-ziplist-size Các tham số được lưu trữ trong trường fill của cấu trú Vì trường fill có kích thước 16 bitsv 88, nên 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 quản lý dữ liệu một cách hiệu quả trong phạm vi giới hạn bởi giá trị tối đa của một số 16 bit.
  • Khi tham số này có giá trị âmđá gà trực tiếp app, độ dài tối đa mà ziplist có thể biểu diễn là 64 KB. Trong khi đó, mỗi phần tử dữ liệu trong ziplist cần ít nhất 2 byte để lưu trữ: 1 byte cho việc... (Chú thích: Tôi đã thay thế "ziplist" bằng phiên bản tiếng Việt tương ứng nếu có, nhưng do thuật ngữ cụ thể này thường không có từ tương đương, tôi giữ nguyên từ gốc để đảm bảo tính chính xác của nội dung kỹ thuật. Nếu cần thay đổi hoàn toàn, vui lòng cho biết thêm yêu cầu chi tiết.) Tôi sẽ tiếp tục phát triển nội dung nếu bạn muốn mở rộng thêm về chủ đề này! prevrawlen đá gà trực tiếp app, trường hợp trường byte data len Hợp nhất làm một; xem thêm data Bây giờ bước vào giai đoạn phân tích mã nguồn. Bài trước Vì vậykeo 88, số lượng mục dữ liệu trong ziplist sẽ không vượt quá 32K, và việc sử dụng 16bit để biểu diễn là đủ. Điều này có nghĩa là mỗi phần tử trong danh sách có thể được quản lý một cách hiệu quả mà không cần đến bộ nhớ lớn hơn, giúp tối ưu hóa không gian lưu trữ một cách đáng kể.

Trên thực tếsv 88, trong cách 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 một số yếu tố khác, do đó 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à, ngay cả khi lý thuyết cho phép một giới hạn cao nhất, các điều kiện thực tế sẽ luôn giữ kích thước ziplist ở mức nhỏ hơn nhiều so với con số lý thuyết này.

Tạo quicklist

Khi chúng ta sử dụng

lệnh để chèn dữ liệu lần đầu tiên vào một list chưa tồn tạikeo 88, Redis sẽ gọi lpush Hoặc rpush giao diện để tạo một quicklist rỗng. quicklistCreate Mã nguồn cho thấyđá gà trực tiếp app, quicklist là một danh sách liên kết hai chiều không có nút trống dư (tất cả

								
									
										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 giới thiệu về cấu trúc dữ liệusv 88, khi triển khai danh sách liên kết hai chiều, người ta thường thêm một nút đầu trống làm đặc biệt, chủ yếu là để thuận tiện cho các thao tác chèn và xóa. Xuất phát từ điều này, ta có thể thấy rằng: Việc thêm nút đầu trống này không chỉ giúp giảm thiểu sự phức tạp trong việc quản lý vị trí của các nút khác mà còn tạo ra một điểm cố định, giúp lập trình viên dễ dàng kiểm soát chuỗi liên kết mà không cần phải lo lắng về các trường hợp viền bỉ như việc kiểm tra xem danh sách có rỗng hay không trước khi thực hiện các thao tác. Với nút đầu trống, mọi hoạt động trên danh sách liên kết trở nên mượt mà hơn, chẳng hạn như việc thêm một phần tử vào đầu danh sách hoặc loại bỏ một phần tử ở giữa đều trở nên dễ dàng và trực quan hơn bao giờ hết. Thêm nữa, việc sử dụng nút đầu trống cũng góp phần làm rõ ràng hơn về mặt logic đối với các cấu trúc phức tạp, đồng thời giảm thiểu các lỗi tiềm ẩn trong quá trình viết mã. Điều này đặc biệt hữu ích khi xử lý các tập dữ liệu lớn hoặc khi chương trình yêu cầu độ tin cậy cao. Nhờ đó, việc bảo trì và mở rộng mã nguồn sau này cũng trở nên đơn giản hơn rất nhiều. quicklistCreate Được khởi tạo thành NULL). head tail Hoạt động push của quicklist

Hoạt động push của quicklist được thực hiện bằng cách gọi

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

								
									
										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)sv 88, thì dữ liệu mới sẽ được chèn trực tiếp vào ziplist (gọi

  • Thực hiệnđá gà trực tiếp app, còn sẽ nén các nút bên trong theo _quicklistNodeAllowInsert Thiết lập. Cách thực hiện khá phức tạpsv 88, chúng tôi sẽ không đi sâu vào đây. ziplistPush )。
  • Nếu danh sách liên kết đơn ziplist tại nút đầu (hoặc nút cuối) quá lớnsv 88, lúc đó sẽ tạo ra một nút quicklistNode mới (và đồng thời cũng tạo ra một ziplist mới). Sau đó, nút quicklistNode này sẽ được chèn vào danh sách liên kết hai chiều quicklist (thực hiện thông qua việc gọi hàm). _quicklistInsertNodeAfter )。

Nếu _quicklistInsertNodeAfter Các hoạt động khác của quicklist list-compress-depth Hoạt động pop của 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ốikeo 88, mà còn thực hiện chèn từ bất kỳ vị trí được chỉ định nào.

Các thao tác của quicklist khá nhiều và chi tiết thực hiện cũng tương đối phức tạpđá gà trực tiếp app, do đó việc phân tích mã nguồn chi tiết sẽ không được đề cập ở đây. Thay vào đó, chúng ta sẽ giới thiệu sơ lược về một số chức năng 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 tiên (hoặc cuối cùng) không vượt quá giới hạn (tức quicklistPopCustom Quá trình này cơ bản ngược lại so với quicklistPush: đầu tiênkeo 88, bạn cần xóa mục dữ liệu tương ứng từ ziplist của nút đầu hoặc cuối. Nếu sau khi xóa mà ziplist trở nên trống rỗng, thì nút đầu hoặc cuối cũng cần được xóa đi. Sau khi thực hiện việc xóa, có khả năng các nút bên trong sẽ cần được giải nén lại để duy trì tính nhất quán trong cấu trúc. Quy trình này đòi hỏi sự cẩn trọng để đảm bảo không làm hỏng bất kỳ phần nào khác của danh sách.

quicklist không chỉ thực hiện chèn từ đầu hoặc cuốisv 88, mà còn thực hiện chèn từ bất kỳ vị trí được chỉ định nào. quicklistInsertAfter quicklistInsertBefore Bạn có thể chèn thêm các mục dữ liệu ngay sau hoặc ngay trước vị trí được chỉ định. Việc thực hiện thao tác chèn dữ liệu vào bất kỳ vị trí nào trong danh sách không hề đơn giảnđá gà trực tiếp app, nó đòi hỏi nhiều nhánh logic khác nhau để xử lý từng trường hợp cụ thể. Một số trường hợp phức tạp hơn, đặc biệt khi vị trí cần chèn nằm ở giữa danh sách hoặc gần cuối cấu trúc dữ liệu, điều này yêu cầu hệ thống phải điều chỉnh và cập nhật hàng loạt liên kết để đảm bảo tính toàn vẹn của toàn bộ dữ liệu.

  • Khi kích thước của ziplist tại vị trí chèn không vượt quá giới hạnđá gà trực tiếp app, 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ạnsv 88, 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 bộ nhớ và duy trì hiệu suất tốt hơn khi hoạt động với các cấu trúc dữ liệu phức tạp như ziplist và
  • Khi kích thước của ziplist tại vị trí chèn vượt quá giới hạnkeo 88, 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, lúc này cần phải tạo một nút quicklist mới để chèn vào. Điều này giúp duy trì hiệu quả lưu trữ và đảm bảo rằng cấu trúc dữ liệu hoạt động ổn định trong mọi tình huống.
  • Trong trường hợp ziplist hiện tại vượt quá giới hạn về kích thước và cần chèn dữ liệu vào giữa ziplist (trường hợp chủ yếu là chèn vào giữa)sv 88, thì việc đầu tiên cần làm là tách ziplist thành hai phần riêng biệt. Sau khi chia tách, bạn có thể tiến hành chèn dữ liệu vào một trong hai phần này một cách hiệu quả hơn. Điều này giúp duy trì hiệu suất hoạt động của cấu trúc dữ liệu và đảm bảo rằng các yêu cầu lưu trữ không vượt quá giới hạn cho phép.

quicklistSetOptions Được sử dụng để thiết lập tham số cấu hình kích thước ziplist ( list-max-ziplist-size list-compress-depth Mã code khá đơn giảnđá gà trực tiếp app, chỉ cần gán các giá trị tương ứng lần lượt cho trường fill và trường compress trong cấu trú Đây là phần việc không quá phức tạp, nhưng đòi hỏi sự cẩn thận để đảm bảo rằng các giá trị được gán đúng vào đúng vị trí mà chúng thuộc về trong cấu trúc này.


Trong bài viết tiếp theođá gà trực tiếp app, chúng ta sẽ cùng tìm hiểu về cấu trúc dữ liệu skiplist và cách nó hỗ trợ cho kiểu dữ liệ Đây là một chủ đề thú vị mà bạn không nên bỏ lỡ, hãy đón chờ nhé!


Bài viết gốcsv 88, vui lòng ghi rõ nguồn và bao gồm mã QR bên dưới! Nếu không, từ chối tái bản!
Liên kết bài viết: /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 "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: 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ận cùng vũ trụ

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