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ứ tư trong loạt bài. Trong phần đầu tiên của bài viếti9bet.com nhận 100k, chúng ta sẽ giới thiệu một cấu trúc dữ liệu nội bộ mới của Redis có tên là ziplist. Sau đó, ở nửa sau của bài viết, chúng ta sẽ thảo luận về cách Redis xây dựng cấu trúc hash mà nó cung cấp bên ngoài dựa trên các thành phần robj, dict và ziplist. Redis luôn tìm cách tối ưu hóa hiệu suất và sử dụng bộ nhớ, do đó việc hiểu rõ về ziplist rất quan trọng. Đây là một cấu trúc đặc biệt được thiết kế để lưu trữ các danh sách liên kết ngắn một cách tiết kiệm không gian. Khi kích thước của danh sách nhỏ và các yêu cầu truy xuất không quá phức tạp, ziplist tỏ ra vô cùng hiệu quả so với các cấu trúc khác như Tiếp theo, chúng ta sẽ khám phá thêm về cách Redis kết hợp ziplist với các thành phần cơ bản khác như robj (đối tượng) và dict (bảng từ điển) để tạo ra các tính năng hash mạnh mẽ. Hash trong Redis không chỉ đơn thuần là một công cụ lưu trữ dữ liệu mà còn là nền tảng cho nhiều ứng dụng thực tế, từ cơ sở dữ liệu đệm đến hệ thống phân tán phức tạp. Với sự hiểu biết về cách các thành phần này hoạt động cùng nhau, bạn sẽ có cái nhìn sâu sắc hơn về cách Redis đạt được hiệu suất cao và khả năng mở rộng linh hoạt trong việc quản lý dữ liệu.
Trong quá trình thảo luậncá cược bóng đá, chúng ta sẽ cùng tìm hiểu thêm về hai cấu hình Redis được đề cập trong phần CẤU HÌNH NÂNG CAO (ADVANCED CONFIG) của tệp redis.conf.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
Phần sau của bài viết này sẽ giải thích chi tiết hai cấu hình này.
Theo định nghĩa chính thức của Redisi9bet.com nhận 100k, ziplist được mô tả như sau (trích từ phần chú thích đầ c):
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer valuescá cược bóng đá, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time.
Ziplist là một danh sách liên kết hai chiều đã được mã hóa theo cách đặc biệti9bet.com nhận 100k, với mục tiêu chính là tối ưu hóa hiệu quả lưu trữ. Ziplist có thể được sử dụng để lưu trữ cả chuỗi ký tự và số nguyên, trong đó các số nguyên sẽ được mã hóa theo dạng biểu diễn nhị phân thực sự thay vì được chuyển thành chuỗi ký tự. Với ziplist, bạn có thể truy xuất hoặc thêm phần tử ở cả hai đầu của danh sách với độ phức tạp thời gian là O(1), điều này giúp tăng tốc đáng kể quá trình xử lý dữ liệu. Một điểm thú vị khác là ziplist còn được tối ưu hóa về mặt không gian lưu trữ nhờ vào việc sử dụng kích thước nhỏ hơn cho từng phần tử, điều này làm cho nó trở thành một lựa chọn lý tưởng khi cần quản lý khối lượng lớn dữ liệu mà không làm tiêu tốn quá nhiều tài nguyên hệ thống.
push
Một quá trình đọc: Quá trình thứ
pop
Thao tác.
Trên thực tếi9bet.com nhận 100k, ziplist thể hiện rõ sự hướng tới hiệu quả lưu trữ của Redis. Một danh sách liên kết hai chiều thông thường sẽ khiến mỗi phần tử chiếm một khối bộ nhớ độc lập và được kết nối với nhau thông qua các con trỏ địa chỉ (hoặc tham chiếu). Cách này không chỉ tạo ra nhiều mảnh vụn bộ nhớ mà còn khiến các con trỏ chiếm thêm không gian bộ nhớ. Ngược lại, ziplist lại sắp xếp các phần tử trong cùng một vùng nhớ liên tục, cho phép toàn bộ cấu trúc chỉ sử dụng một khối lớn bộ nhớ duy nhất. Nó giống như một danh sách (list), nhưng thực chất không phải là một danh sách liên kết (linked list) theo nghĩa truyền thống. Thêm vào đó, việc này giúp tối ưu hóa đáng kể không gian lưu trữ và giảm thiểu các vấn đề về bộ nhớ rời rạc.
Ngoài racá cược bóng đá, ziplist được tối ưu hóa để tiết kiệm bộ nhớ ở mức chi tiết nhất bằng cách sử dụng phương pháp mã hóa biến dài cho việc lưu trữ giá trị. Ý nghĩa cơ bản của điều này là với những số nguyên lớn, nó sẽ cần nhiều byte hơn để lưu trữ, trong khi với các số nhỏ, chỉ cần ít byte hơn. Chúng ta sẽ thảo luận chi tiết về những khía cạnh kỹ thuật này ngay sau đây. Trong thực tế, cách tiếp cận này không chỉ giúp giảm thiểu đáng kể lượng bộ nhớ tiêu thụ mà còn đảm bảo rằng dữ liệu vẫn có thể được xử lý một cách hiệu quả. Điều này đặc biệt hữu ích khi phải quản lý một khối lượng lớn dữ liệu trong môi trường tài nguyên hạn chế.
Trong bài viết nàyxem ngoại hạng anh, chúng ta sẽ tập trung cấu trúc dữ liệu của ziplist. Thực tế, ziplist có phần phức tạp hơn so với những gì ban đầu nhìn thấy. Điểm khó hiểu chính nằm ở cách định nghĩa cấu trúc dữ liệu của nó. Tuy nhiên, một khi đã nắm rõ được bản chất của cấu trúc này, thì việc hiểu và thực hiện các thao tác trên ziplist cũng sẽ trở nên đơn giản hơn nhiều.
Tiếp theoi9bet.com nhận 100k, chúng ta sẽ bắt đầu bằng cách trình bày khái quát về định nghĩa cấu trúc dữ liệu ziplist, sau đó đưa ra một ví dụ thực tế để giải thích cách ziplist được cấu thành. Nếu bạn đã hiểu phần này, thì bạn đã hoàn thành được phần lớn mục tiêu của bài viết rồi đấy.
Nhìn tổng quancá cược bóng đá, cấu trúc bộ nhớ của ziplist như sau:
<zlbytes><zltail><zllen><entry>...<entry><zlend></zlend></entry></entry></zllen></zltail></zlbytes>
Các phần khác nhau trong bộ nhớ là liền kề nhaucá cược bóng đá, ý nghĩa cụ thể của từng phần như sau:
<zlbytes></zlbytes>
: 32bitxem ngoại hạng anh, biểu thị tổng số byte mà ziplist chiếm (bao gồm cả
<zlbytes></zlbytes>
chính nó chiếm 4 byte).
<zltail></zltail>
Trong trường hợp nàycá cược bóng đá, việc sử dụng định dạng 32 bit nhằm thể hiện khoảng cách bằng byte giữa phần tử cuối cùng (entry) trong bảng ziplist và vị trí bắt đầu của ziplist. Đây là một cách hiệu quả để lưu trữ thông tin về vị trí của các phần tử bên trong cấu trúc dữ liệu, giúp quá trình truy xuất trở nên nhanh chóng và chính xác hơn.
<zltail></zltail>
Sự hiện diện của nó cho phép chúng ta dễ dàng xác định phần tử cuối cùng trong ziplist mà không cần duyệt qua toàn bộ danh sáchcá cược bóng đá, nhờ đó giúp thực hiện các thao tác push hoặc pop ở cuối ziplist một cách nhanh chóng và hiệu quả.
<zllen></zllen>
Trong trường hợp nàyxem ngoại hạng anh, trường zllen với kích thước chỉ 16 bit có thể biểu diễn giá trị tối đa là 2^16 - 1. Tuy nhiên, điều quan trọng cần lưu ý là nếu số lượng mục (entry) trong ziplist vượt quá giá trị mà 16 bit có thể biểu diễn, ziplist vẫn có khả năng xử lý tình huống này. Cách thực hiện như sau: Khi số lượng mục vượt khỏi giới hạn của 16 bit, ziplist sẽ sử dụng một quy ước đặc biệt để giải quyết vấn đề này. Theo quy ước đó, nếu...
<zllen></zllen>
Nếu số lượng mục dữ liệu trong ziplist nhỏ hơn hoặc bằng 2^16-2 (tức là không phải 2^16-1)i9bet.com nhận 100k, thì
<zllen></zllen>
điều này biểu thị số lượng mục dữ liệu trong ziplist; nếu khôngi9bet.com nhận 100k, tức là
<zllen></zllen>
trường hợp 16bit toàn bộ là 1cá cược bóng đá, thì
<zllen></zllen>
Trong trường hợp nàycá cược bóng đá, nếu muốn biết tổng số lượng các mục trong ziplist mà không biểu thị rõ số lượng ban đầu, bạn sẽ cần phải duyệt qua từng mục từ đầu đến cuối của ziplist để đếm số lượng một cách thủ công. Quá trình này đòi hỏi phải thực hiện quét toàn bộ danh sách để có được con số chính xác.
<entry></entry>
Điểm quan trọng ở đây là các mục chứa dữ liệu thực sựi9bet.com nhận 100k, với độ dài không cố định. Mỗi mục dữ liệu (entry) lại có cấu trúc nội bộ riêng của nó, và chúng ta sẽ đi sâu vào chi tiết này sau.
<zlend></zlend>
: Byte cuối cùng trong ziplist là một dấu kết thúccá cược bóng đá, giá trị cố định bằng 255.
Điểm đáng chú ý trong định nghĩa trên còn có:
<zlbytes></zlbytes>
,
<zltail></zltail>
,
<zllen></zllen>
Khi một giá trị chiếm nhiều bytecá cược bóng đá, việc lưu trữ sẽ có sự khác biệt giữa định dạng big endian (đại diện cho thứ tự cao đến thấp) và little endian (thứ tự thấp đến cao). Ziplist sử dụng định dạng little endian để lưu trữ, điều này sẽ được giải thích rõ hơn khi chúng ta đi vào các ví dụ cụ thể sau đây.
Hãy nhìn kỹ cấu trúc của mỗi mục dữ liệu
<entry></entry>
thành phần:
<prevrawlen><len><data></data></len></prevrawlen>
Chúng ta thấy rằng trước dữ liệu thực tế (
<data></data>
) còn có hai trường:
<prevrawlen></prevrawlen>
Trường này cho biết tổng số byte mà mục dữ liệu trước đó chiếm dụng. Mục đích của trường này là giúp ziplist có thể duyệt ngược từ cuối về đầu (từ vị trí của mục hiện tạii9bet.com nhận 100k, chỉ cần di chuyển lùi lại prevrawlen byte, ta sẽ tìm thấy mục trước đó). Phương pháp mã hóa của trường này sử dụng định dạng biến dài (variable-length encoding), cho phép tối ưu hóa không gian lưu trữ tùy theo giá trị cụ thể.
<len></len>
: Biểu thị độ dài của mục dữ liệu hiện tại (tức
<data></data>
phần độ dài). Cũng sử dụng mã hóa biến dài.
Trước tiên nói
<prevrawlen></prevrawlen>
Một quá trình đọc: Quá trình thứ
<len></len>
Làm thế nào để thực hiện mã hóa biến đổi độ dài đây? Thưa quý độc giảcá cược bóng đá, hãy tập trung tinh thần vì chúng ta sắp đến phần phức tạp nhất trong định nghĩa ziplist rồi.
. Nó có hai khả năngi9bet.com nhận 100k, hoặc là 1 byte, hoặc là 5 byte:
<prevrawlen></prevrawlen>
Nếu mục dữ liệu trước đó chiếm ít hơn 254 bytexem ngoại hạng anh, thì
<prevrawlen></prevrawlen>
Nếu mục dữ liệu trước đó chiếm từ 254 byte trở lêncá cược bóng đá, thì
<prevrawlen></prevrawlen>
Dùng 5 byte để biểu diễnxem ngoại hạng anh, trong đó byte đầu tiên có giá trị là 254 (dùng làm dấu hiệu cho trường hợp này), và 4 byte tiếp theo tạo thành một giá trị nguyên, dùng để lưu trữ chính xác số byte mà mục dữ liệu trước đó chiếm dụng.Điều này là do: 255 đã được định nghĩa là dấu kết thúc của ziplist
(byte đầu tiên) không thể lấy giá trị 255xem ngoại hạng anh, nếu không sẽ xảy ra xung đột.
<zlend></zlend>
Trong thực hiện của nhiều thao tác liên quan đến ziplistxem ngoại hạng anh, việc kiểm tra xem liệu giá trị đầu tiên của một mục dữ liệu có phải là byte 255 hay không thường được sử dụng để xác định xem liệu đã đến cuối ziplist hay chưa. Do đó, byte đầu tiên của một mục dữ liệu bình thường (cũng chính là) sẽ đóng vai trò như một dấu hiệu đặc biệt để nhận diện và phân loại các loại giá trị khác nhau trong dãy này.
<prevrawlen></prevrawlen>
|00pppppp| - 1 byte. Byte đầu tiên cao hai bit là 00cá cược bóng đá, vậy
tất cả đều được lưu trữ dưới dạng chuỗi; bắt đầu từ trường hợp thứ tư bên dướicá cược bóng đá,
<len></len>
Các trường dữ liệu trở nên phức tạp hơnxem ngoại hạng anh, chúng được chia thành 9 trường hợp khác nhau tùy thuộc vào giá trị của byte đầu tiên (cách biểu diễn dưới đây được thể hiện theo hệ nhị phân):
<len></len>
Trường dữ liệu chỉ có một bytexem ngoại hạng anh, trong khi đó 6 bit còn lại được sử dụng để biểu diễn giá trị độ dài, với giá trị tối đa có thể đạt đến là 63 (2^6 - 1). Điều này cho phép hệ thống tối ưu hóa việc quản lý không gian lưu trữ và tăng hiệu suất xử lý thông tin một cách đáng kể.
<len></len>
Trường dữ liệu chiếm 2 bytexem ngoại hạng anh, trong đó có tổng cộng 14 bit được sử dụng để biểu thị giá trị độ dài. Giá trị lớn nhất mà nó có thể biểu diễn là 16383 (2^14 - 1). Điều này cho phép hệ thống xử lý các dãy số có phạm vi rộng, từ 0 đến gần 16 nghìn, đáp ứng tốt nhu cầu lưu trữ thông tin trong nhiều ứng dụng khác nhau.
<data></data>
Trường này chiếm 1 bytecá cược bóng đá, giá trị là 0xC0, dữ liệu sau đó
<data></data>
được lưu trữ dưới dạng kiểu int16_t có 2 byte.
<len></len>
Trường này chiếm 1 bytei9bet.com nhận 100k, giá trị là 0xD0, dữ liệu sau đó
<data></data>
được lưu trữ dưới dạng kiểu int32_t có 4 byte.
<len></len>
Trường này chiếm 1 bytei9bet.com nhận 100k, giá trị là 0xE0, dữ liệu sau đó
<data></data>
được lưu trữ dưới dạng kiểu int64_t có 8 byte.
<len></len>
Trường này chiếm 1 bytecá cược bóng đá, giá trị là 0xF0, dữ liệu sau đó
<data></data>
được lưu trữ dưới dạng số nguyên có 3 byte.
<len></len>
Trường này chiếm 1 bytexem ngoại hạng anh, giá trị là 0xFE, dữ liệu sau đó
<data></data>
được lưu trữ dưới dạng số nguyên có 1 byte.
<len></len>
Trường này để biểu thị dữ liệu thực sựcá cược bóng đá, thay vào đó là
<data></data>
Rồixem ngoại hạng anh, định nghĩa cấu trúc dữ liệu ziplist, chúng ta đã giới thiệu xong, bây giờ hãy xem một ví dụ cụ thể.
<data></data>
Hình trên là một mẫu ziplist thật. Chúng ta sẽ giải thích từng mục một:
<len></len>
Một quá trình đọc: Quá trình thứ
<data></data>
Hai phần đã kết hợp thành một. Ngoài racá cược bóng đá, do xxxx chỉ có thể nhận 13 giá trị là 0001 và 1101 (các giá trị khác đều bị xung đột với các trường hợp khác, ví dụ như 0000 và 1110 đều mâu thuẫn với tình huống thứ 7 và thứ 8 trước đó, trong khi 1111 lại trùng với dấu hiệu kết thúc), và vì giá trị thập phân nhỏ cần bắt đầu từ 0, nên 13 giá trị này sẽ biểu thị từ 0 đến 12. Điều đó có nghĩa là giá trị của xxxx trừ đi 1 sẽ chính là giá trị số nguyên mà nó cần thể hiện.Trường. Điều gì là little-endian? Có nghĩa là byte thấp của dữ liệu được lưu trữ ở địa chỉ bộ nhớ thấp hơn (tham khảo mục từ điển Wikipedia
). Do đóxem ngoại hạng anh, giá trị ở đây
<zlbytes></zlbytes>
nên được giải mã thành 0x00000021xem ngoại hạng anh, biểu diễn bằng thập phân chính xác là 33.
Endianness
Tiếp theo 4 byte (byte[4..7]) là
<zlbytes></zlbytes>
Byte cuối cùng (byte[32]) biểu thị
<zltail></zltail>
Bạn có thể giải thích bằng cách sử dụng chế độ lưu trữ little-endiancá cược bóng đá, trong đó giá trị là 0x0000001D (tương đương với 29). Điều này có nghĩa là phần tử dữ liệu cuối cùng nằm ở vị trí byte[29] (phần tử dữ liệu này có giá trị là 0x05FE14).
<zlend></zlend>
Chuỗi: "name"
Chuỗi: "tielei"
lệnh. Điều này chúng ta sẽ đề cập lại ở phần sau.
Tiếp theo tôi sẽ dán một số đoạn mã.
hset
Giao diện ziplist
Được rồixem ngoại hạng anh, vì bạn vẫn đang đọc đến đây, có thể thấy bạn khá kiên nhẫn đấy (thực ra tôi viết đến đoạn này cũng mệt muốn chết rồi). Bạn có thể lưu bài viết này lại trước, nghỉ ngơi một lát, và quay lại đọc phần sau khi đã hồi phục sức lực.
Trước tiên chúng ta không vội xem cách triển khaixem ngoại hạng anh, trước hết hãy chọn một vài giao diện quan trọng của ziplist, xem chúng trông như thế nào:
ziplistNew: Tạo một ziplist rỗng (chỉ chứa
unsigned
char
*
ziplistNew
(
void
);
unsigned
char
*
ziplistMerge
(
unsigned
char
**
first
,
unsigned
char
**
second
);
unsigned
char
*
ziplistPush
(
unsigned
char
*
zl
,
unsigned
char
*
s
,
unsigned
int
slen
,
int
where
);
unsigned
char
*
ziplistIndex
(
unsigned
char
*
zl
,
int
index
);
unsigned
char
*
ziplistNext
(
unsigned
char
*
zl
,
unsigned
char
*
p
);
unsigned
char
*
ziplistPrev
(
unsigned
char
*
zl
,
unsigned
char
*
p
);
unsigned
char
*
ziplistInsert
(
unsigned
char
*
zl
,
unsigned
char
*
p
,
unsigned
char
*
s
,
unsigned
int
slen
);
unsigned
char
*
ziplistDelete
(
unsigned
char
*
zl
,
unsigned
char
**
p
);
unsigned
char
*
ziplistFind
(
unsigned
char
*
p
,
unsigned
char
*
vstr
,
unsigned
int
vlen
,
unsigned
int
skip
);
unsigned
int
ziplistLen
(
unsigned
char
*
zl
);
ziplistMerge: Hợp nhất hai ziplist thành một ziplist mới.
<zlbytes><zltail><zllen><zlend></zlend></zllen></zltail></zlbytes>
)。
Các giao diện liên quan đến ziplistcá cược bóng đá, cụ thể là cách chúng hoạt động, vẫn khá phức tạp. Do giới hạn về phạm vi nội dung, ở đây chúng ta sẽ chỉ tập trung giải thích logic chèn dữ liệu thông qua mã nguồn. Thao tác chèn là một ví dụ điển hình, qua đó ta có thể hiểu rõ hơn về cách ziplist được thực hiện bên trong. Khi đã nắm vững phần này, việc hiểu các phần còn lại của ziplist cũng sẽ trở nên dễ dàng hơn nhiều. Mặc dù ziplist có vẻ phức tạp khi nhìn vào toàn bộ cấu trúc, nhưng nếu đi sâu vào từng chi tiết nhỏ, như thao tác chèn dữ liệu, bạn sẽ thấy rằng nó không quá khó để hiểu. Điều quan trọng là cần có sự kiên nhẫn và tập trung để theo dõi từng bước thực hiện mà mã nguồn đang thực hiện. Chính vì điều này, việc học hỏi từ những phần đơn giản nhất sẽ giúp bạn dễ dàng tiếp cận với các chức năng nâng cao hơn trong tương lai.
Cả hai hàm ziplistPush và ziplistInsert đều có chức năng chèn dữ liệuxem ngoại hạng anh, nhưng sự khác biệt nằm ở cách chúng xác định vị trí để chèn. Cụ thể, cả hai đều sử dụng một hàm hỗ trợ nội bộ tên là __ziplistInsert để thực hiện việc chèn. Dưới đây là đoạn mã nguồn của hàm này (trích từ tệp ziplist.c): ```c int __ziplistInsert(ziplist *zl, zlentry *p, unsigned char *s, unsigned int slen) { // Đoạn mã xử lý chèn dữ liệu vào danh sách liên kết } ``` Hàm __ziplistInsert đóng vai trò quan trọng trong việc duy trì tính toàn vẹn của cấu trúc dữ liệu ziplist, đảm bảo rằng các phần tử được thêm vào đúng vị trí mong muốn mà không làm phá vỡ cấu trúc tổng thể.
static
unsigned
char
*
__ziplistInsert
(
unsigned
char
*
zl
,
unsigned
char
*
p
,
unsigned
char
*
s
,
unsigned
int
slen
)
{
size_t
curlen
=
intrev32ifbe
(
ZIPLIST_BYTES
(
zl
)),
reqlen
;
unsigned
int
prevlensize
,
prevlen
=
0
;
size_t
offset
;
int
nextdiff
=
0
;
unsigned
char
encoding
=
0
;
long
long
value
=
123456789
;
/* initialized to avoid warning. Using a value
that is easy to see if for some reason
we use it uninitialized. */
zlentry
tail
;
/* Find out prevlen for the entry that is inserted. */
if
(
p
[
0
]
!=
ZIP_END
)
{
ZIP_DECODE_PREVLEN
(
p
,
prevlensize
,
prevlen
);
}
else
{
unsigned
char
*
ptail
=
ZIPLIST_ENTRY_TAIL
(
zl
);
if
(
ptail
[
0
]
!=
ZIP_END
)
{
prevlen
=
zipRawEntryLength
(
ptail
);
}
}
/* See if the entry can be encoded */
if
(
zipTryEncoding
(
s
,
slen
,
&
value
,
&
encoding
))
{
/* 'encoding' is set to the appropriate integer encoding */
reqlen
=
zipIntSize
(
encoding
);
}
else
{
/* 'encoding' is untouchedxem ngoại hạng anh, however zipEncodeLength will use the
* string length to figure out how to encode it. */
reqlen
=
slen
;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
reqlen
+=
zipPrevEncodeLength
(
NULL
,
prevlen
);
reqlen
+=
zipEncodeLength
(
NULL
,
encoding
,
slen
);
/* When the insert position is not equal to the tailcá cược bóng đá, we need to
* make sure that the next entry can hold this entry's length in
* its prevlen field. */
nextdiff
=
(
p
[
0
]
!=
ZIP_END
)
?
zipPrevLenByteDiff
(
p
,
reqlen
)
:
0
;
/* Store offset because a realloc may change the address of zl. */
offset
=
p
-
zl
;
zl
=
ziplistResize
(
zl
,
curlen
+
reqlen
+
nextdiff
);
p
=
zl
+
offset
;
/* Apply memory move when necessary and update tail offset. */
if
(
p
[
0
]
!=
ZIP_END
)
{
/* Subtract one because of the ZIP_END bytes */
memmove
(
p
+
reqlen
,
p
-
nextdiff
,
curlen
-
offset
-
1
+
nextdiff
);
/* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength
(
p
+
reqlen
,
reqlen
);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET
(
zl
)
=
intrev32ifbe
(
intrev32ifbe
(
ZIPLIST_TAIL_OFFSET
(
zl
))
+
reqlen
);
/* When the tail contains more than one entrycá cược bóng đá, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
zipEntry
(
p
+
reqlen
,
&
tail
);
if
(
p
[
reqlen
+
tail
.
headersize
+
tail
.
len
]
!=
ZIP_END
)
{
ZIPLIST_TAIL_OFFSET
(
zl
)
=
intrev32ifbe
(
intrev32ifbe
(
ZIPLIST_TAIL_OFFSET
(
zl
))
+
nextdiff
);
}
}
else
{
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET
(
zl
)
=
intrev32ifbe
(
p
-
zl
);
}
/* When nextdiff != 0xem ngoại hạng anh, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if
(
nextdiff
!=
0
)
{
offset
=
p
-
zl
;
zl
=
__ziplistCascadeUpdate
(
zl
,
p
+
reqlen
);
p
=
zl
+
offset
;
}
/* Write the entry */
p
+=
zipPrevEncodeLength
(
p
,
prevlen
);
p
+=
zipEncodeLength
(
p
,
encoding
,
slen
);
if
(
ZIP_IS_STR
(
encoding
))
{
memcpy
(
p
,
s
,
slen
);
}
else
{
zipSaveInteger
(
p
,
value
,
encoding
);
}
ZIPLIST_INCR_LENGTH
(
zl
,
1
);
return
zl
;
}
. Độ dài này sẽ được lưu vào mục dữ liệu mới được chèn vào
<zlend></zlend>
。
prevlen
i9bet.com nhận 100k, nó bao gồm ba phần:
<prevrawlen></prevrawlen>
trường.
reqlen
Trước tiên thử chuyển đổi thành số nguyên.
<prevrawlen></prevrawlen>
,
<len></len>
Ngoài việc mục dữ liệu cần chèn gây ra nhu cầu bổ sung bộ nhớ cho ziplistcá cược bóng đá, còn phải xem xét kích thước của mục dữ liệu tại vị trí p trước khi chèn (nay sẽ xếp sau mục dữ liệu cần chèn), điều này được tính toán bằng cách gọi
zipTryEncoding
Nếu tăng lên,
reqlen
Giờ đây rất dễ tính toán xem ziplist mới cần bao nhiêu byte sau khi chènxem ngoại hạng anh, sau đó gọi
<prevrawlen></prevrawlen>
Sự thay đổi trong trường dữ liệu này. Trước đâyi9bet.com nhận 100k, nó được thiết kế để lưu trữ tổng độ dài của mục trước đó, nhưng bây giờ đã chuyển thành việc lưu giữ tổng độ dài của mục dữ liệu đang được chèn vào. Điều này giúp cho việc quản lý và theo dõi dữ liệu trở nên linh hoạt hơn, đồng thời giảm thiểu nguy cơ lỗi khi xử lý các bản ghi liên tiếp. Nhờ vậy, hiệu suất hoạt động của hệ thống cũng được cải thiện đáng kể.
<prevrawlen></prevrawlen>
Kích thước không gian lưu trữ mà trường dữ liệu cần có thể thay đổicá cược bóng đá, sự thay đổi này có thể tăng lên hoặc giảm xuống. Điều đáng chú ý là mức độ thay đổi này thực tế đã đạt đến đâu và ảnh hưởng ra sao đến hiệu suất tổng thể của hệ thống. Một khi kích thước thay đổi, các yếu tố liên quan như cách quản lý bộ nhớ, tối ưu hóa truy xuất dữ liệu và khả năng mở rộng hệ thống cũng cần được xem xét kỹ lưỡng hơn nữa. Có thể nói, việc theo dõi và điều chỉnh kích thước trường dữ liệu chính là một khía cạnh quan trọng trong việc duy trì và nâng cấp hiệu quả hoạt động của cơ sở dữ liệu.
nextdiff
để điều chỉnh kích thước lại. Trong việc triển khai của ziplistResize sẽ gọi allocator
zipPrevLenByteDiff
cá cược bóng đá, điều này có thể dẫn đến sao chép dữ liệu.
nextdiff
Trường. Ngoài rai9bet.com nhận 100k, có thể cần điều chỉnh kích thước của ziplist
ziplistResize
Hash và ziplist
zrealloc
, hoặc
<prevrawlen></prevrawlen>
)xem ngoại hạng anh, cũng hỗ trợ truy xuất riêng lẻ theo một field cụ thể (theo
<zltail></zltail>
trường.
Trong Rediscá cược bóng đá, kiểu dữ liệu hash được xem là một lựa chọn rất phù hợp để lưu trữ cấu trúc của một đối tượng. Mỗi thuộc tính của đối tượng có thể dễ dàng ánh xạ tương ứng với một field trong cấu trúc hash. Điều này giúp việc quản lý và truy xuất thông tin trở nên đơn giản và hiệu quả. Ngoài ra, việc sử dụng hash còn cho phép tối ưu hóa không gian lưu trữ, đồng thời tăng tốc độ truy vấn dữ liệu khi làm việc với các đối tượng phức tạp.
Chúng ta thường dễ dàng tìm thấy những bài viết kỹ thuật trên mạng nói rằng việc lưu trữ một đối tượng bằng hash sẽ giúp tiết kiệm bộ nhớ hơn so với string. Tuy nhiêncá cược bóng đá, điều này không hoàn toàn đúng trong mọi trường hợp mà cần có những điều kiện cụ thể. Điều này phụ thuộc vào cách mà đối tượng được lưu trữ. Nếu bạn lưu các thuộc tính của đối tượng vào nhiều key riêng biệt (mỗi giá trị thuộc tính được lưu dưới dạng string), tất nhiên nó sẽ chiếm nhiều bộ nhớ hơn. Nhưng nếu bạn sử dụng một số phương pháp Serialization, chẳng hạn như: - **JSON Serialization**: Đây là một phương pháp phổ biến để chuyển đổi đối tượng thành chuỗi JSON. Khi sử dụng JSON, dữ liệu sẽ được tối ưu hóa để giảm thiểu kích thước tổng thể, nhờ đó giúp tiết kiệm bộ nhớ. - **Protobuf (Protocol Buffers)**: Công nghệ này do Google phát triển, cho phép định nghĩa cấu trúc dữ liệu và mã hóa nó thành dạng nhị phân. So với các định dạng text truyền thống như XML hay JSON, Protobuf thường nhỏ gọn hơn rất nhiều. - **Avro**: Một hệ thống Serialization khác, được thiết kế để lưu trữ và xử lý dữ liệu lớn. Avro cũng giúp tối ưu hóa bộ nhớ và tốc độ xử lý. Mỗi phương pháp Serialization đều có những ưu nhược điểm riêng, nhưng nhìn chung chúng đều giúp tối ưu hóa tài nguyên và cải thiện hiệu suất khi làm việc với các đối tượng lớn. Điều quan trọng là phải hiểu rõ đặc thù của từng phương pháp để chọn lựa phù hợp nhất cho dự án của mình. Protocol Buffers nhưng Apache Thrift Khi bạn đầu tiên chuyển đối tượng thành mảng bytecá cược bóng đá, sau đó lưu vào chuỗi của Redis, thì so với việc sử dụng hash, loại nào sẽ tiết kiệm bộ nhớ hơn chưa chắc đã có câu trả lời rõ ràng. Điều này tùy thuộc vào bản chất của đối tượng và cách mà Redis xử lý từng loại dữ liệu. Nếu đối tượng có cấu trúc phức tạp, sử dụng hash có thể tối ưu hơn vì nó cho phép truy cập trực tiếp vào các trường riêng lẻ mà không cần giải mã toàn bộ dữ liệu. Ngược lại, nếu dữ liệu có xu hướng đồng nhất, lưu dưới dạng chuỗi có thể là phương án hiệu quả hơn trong một số trường hợp nhất định.
Dựa trên cách tiếp cận lưu trữ hash thay vì chuỗi được sinh ra từ việc tuần tự hóaxem ngoại hạng anh, hash vẫn có những lợi thế nhất định khi thực hiện các lệnh thao tác: nó không chỉ cho phép truy xuất và cập nhật nhiều trường cùng lúc mà còn mang lại hiệu suất cao hơn trong việc xử lý dữ liệu.
hmset
/
hmget
đối tượng robj.
hset
/
hget
)。
Trên thực tếcá cược bóng đá, khi dữ liệu ngày càng lớn, cách thức triển khai cấu trúc dữ liệu bên dưới hash sẽ thay đổi, và tất nhiên hiệu suất lưu trữ cũng sẽ khác nhau. Khi số lượng trường (field) ít và các giá trị (value) cũng tương đối nhỏ, hash thường được thực hiện bằng ziplist; còn khi số lượng trường tăng lên và giá trị trở nên lớn hơn, hash có thể chuyển sang sử dụng dict để triển khai. Khi hash được thực hiện bằng dict, hiệu suất lưu trữ của nó sẽ không thể so sánh với các phương phá Điều này xảy ra vì dict được tối ưu hóa cho việc truy xuất nhanh nhưng lại tốn kém hơn về mặt bộ nhớ so với ziplist, vốn là một cấu trúc dữ liệu tiết kiệm bộ nhớ hơn trong trường hợp các giá trị nhỏ và ít trường.
Thực tếxem ngoại hạng anh, ví dụ ziplist được đưa ra ở phần trước của bài viết này, được xây dựng bởi hai lệnh sau đây.
hset key field value
Khi thực hiện lệnhcá cược bóng đá, Redis sẽ tạo ra một cấu trúc hash, và hash mới này về cơ bản được xây dựng dựa trên một ziplist. Ziplist là một dạng danh sách có cấu trúc đặc biệt trong Redis, giúp lưu trữ các cặp khóa-giá trị với hiệu suất cao trong trường hợp dữ liệu vừa phải hoặc ít phần tử.
robj
*
createHashObject
(
void
)
{
unsigned
char
*
zl
=
ziplistNew
();
robj
*
o
=
createObject
(
OBJ_HASH
,
zl
);
o
->
encoding
=
OBJ_ENCODING_ZIPLIST
;
return
o
;
}
Mỗi lần thực hiện
createHashObject
Hàm nàycá cược bóng đá, nằm trong tệp object.c, có nhiệm vụ chính là tạo ra một cấu trúc hash mới. Có thể thấy rằng, nó đã khởi tạo một đối tượng với các thuộc tính cần thiết để có thể hoạt động như một bảng băm hiệu quả. Cấu trúc này sẽ đóng vai trò quan trọng trong việc lưu trữ và quản lý dữ liệu theo cách có thứ tự, cho phép truy xuất nhanh chóng thông qua các khóa duy nhất. Điều này giúp cải thiện đáng kể hiệu suất khi xử lý khối lượng lớn dữ liệu trong hệ thống.
type = OBJ_HASH
tạo ra hai mục dữ liệu).
encoding = OBJ_ENCODING_ZIPLIST
Ý nghĩa của cấu hình này là khi một trong hai điều kiện sau đây được đáp ứngi9bet.com nhận 100k, ziplist sẽ chuyển thành dict:
hàm).
hset user:100 name tielei
hset user:100 age 20
Mỗi lần chèn hoặc sửa đổi gây ra realloc sẽ có nhiều khả năng hơn để sao chép bộ nhớcá cược bóng đá, từ đó làm giảm hiệu suất.
hset
Bạn có thể chèn trường (field) và giá trị (value) được chỉ định vào một mục dữ liệu mới và thêm nó vào danh sách liên kết (ziplist). Điều này đồng nghĩa với việc mỗi lần...
hset
Một khi xảy ra sao chép bộ nhớxem ngoại hạng anh, chi phí sao chép bộ nhớ cũng sẽ tăng lên, vì phải sao chép một khối dữ liệu lớn hơn.
Khi có thêm dữ liệuxem ngoại hạng anh, cấu trúc ziplist ở tầng dưới cùng của hàm băm (hash) có thể sẽ chuyển đổi thành dạng dict. Nhưng câu hỏi đặt ra là, cần phải thêm bao nhiêu dữ liệu thì mới xảy ra sự chuyển đổi này? Điều này phụ thuộc vào các yếu tố như kích thước tối đa của ziplist và mật độ lưu trữ thông tin. Nếu lượng dữ liệu vượt quá giới hạn cho phép, hệ thống sẽ tự động chuyển sang sử dụng dict để đảm bảo hiệu suất và tính ổn định. Tuy nhiên, việc xác định chính xác con số cụ thể thường không được công khai rõ ràng trong tài liệu, mà thường dựa trên các thuật toán nội bộ của phần mềm.
Nhớ lại hai cấu hình Redis mà chúng tôi đề cập ở đầu bài không?
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
Bài viết tiếp theo chúng tôi sẽ giới thiệu quicklistxem ngoại hạng anh, xin hãy chờ đợi.
hashTypeSet
Một khi xảy ra sao chép bộ nhớcá cược bóng đá, chi phí sao chép bộ nhớ cũng sẽ tăng lên, vì phải sao chép một khối dữ liệu lớn hơn.
hashTypeTryConversion
Một khi xảy ra sao chép bộ nhới9bet.com nhận 100k, chi phí sao chép bộ nhớ cũng sẽ tăng lên, vì phải sao chép một khối dữ liệu lớn hơn.Việc Redis thiết kế kiểu hash như vậy là do khi ziplist trở nên quá lớnxem ngoại hạng anh, nó sẽ gặp một số hạn chế đáng kể. Đầu tiên, việc xử lý dữ liệu trong ziplist sẽ trở nên chậm hơn bởi vì hệ thống phải duyệt qua nhiều phần tử hơn để tìm ra giá trị mong muốn. Thứ hai, mức tiêu thụ bộ nhớ có thể tăng lên bất thường, dẫn đến hiệu suất tổng thể của hệ thống bị ảnh hưởng. Cuối cùng, việc cập nhật hoặc thêm mới các phần tử vào ziplist lớn sẽ mất nhiều thời gian hơn và có thể gây ra hiện tượng treo tạm thời đối với các tác vụ khác.
Tóm lạixem ngoại hạng anh, ziplist được thiết kế với ý tưởng rằng các mục dữ liệu sẽ nằm liền kề nhau để tạo thành không gian bộ nhớ liên tục. Kiểu cấu trúc này không thực sự hiệu quả khi thực hiện các thao tác chỉnh sửa. Khi dữ liệu bị thay đổi, nó có thể dẫn đến việc tái phân bổ bộ nhớ realloc, từ đó có khả năng gây ra việc sao chép bộ nhớ. Điều này có thể làm ảnh hưởng đến hiệu suất và gây ra sự chậm trễ trong quá trình xử lý.
quicklist