Kể từ khi tôi viết bài... Phân tích cấu trúc dữ liệu nội bộ của Redis git clone https://github.com/redis/redis.git
Redis được viết bằng ngôn ngữ Ctai ban ca, và khi bắt đầu đọc mã nguồn của nó, tất nhiên bạn nên bắt đầu từ hàm main. Tuy nhiên, khi đọc, bạn cần nắm vững một đường dây chính: đó là khi chúng ta gửi một lệnh vào Redis, mã nguồn sẽ thực hiện theo trình tự nào. Bằng cách này, trước tiên bạn có thể quan sát từ bên ngoài, thử chạy một số lệnh và hiểu rõ cách hoạt động bên ngoài của các lệnh đó. Sau đó, bạn có thể đi sâu hơn để xem mã nguồn tương ứng đã được triển khai như thế nào. Để hiểu được mã nguồn này, trước hết bạn cần hiểu rõ cơ chế sự kiện của Redis. Và khi hiểu rõ cơ chế vòng lặp sự kiện (Event Loop) của Redis, bạn sẽ còn giải đáp được một câu hỏi thú vị: Tại sao Redis lại chỉ sử dụng một luồng mà vẫn có thể xử lý nhiều yêu cầu cùng lúc? (Dĩ nhiên, nói một cách nghiêm ngặt thì Redis không phải chỉ chạy duy nhất một luồng; nhưng ngoài luồng chính ra, các luồng khác của Redis chỉ đóng vai trò hỗ trợ, là những luồng chạy nền để thực hiện các tác vụ tốn thời gian một cách bất đồng). Bạn cũng cần lưu ý rằng, mặc dù Redis chủ yếu hoạt động trên một luồng chính, các yêu cầu sẽ được xử lý tuần tự và không bị gián đoạn bởi các tác vụ nền. Điều này giúp tăng cường hiệu quả và tối ưu hóa trong việc quản lý tài nguyên. Khi hiểu được điều này, bạn sẽ nhận ra rằng Redis đã được thiết kế rất thông minh để tận dụng tối đa hiệu suất mà không cần sử dụng nhiều luồng phức tạp. Hãy tưởng tượng rằng bạn đang đứng trước một cỗ máy khổng lồ với vô số bộ phận chuyển động. Bạn không cần phải hiểu tất cả mọi thứ ngay lập tức, nhưng nếu bạn biết cách quan sát từ bên ngoài, sau đó dần dần khám phá từng phần bên trong, bạn sẽ hiểu rõ hơn về cách nó vận hành. Với Redis, việc hiểu rõ sự kiện và vòng lặp sự kiện chính là chìa khóa để mở cánh cửa dẫn đến thế giới của mã nguồn đầy thú vị này.
Từ hàm mainkeo 88, chúng ta có thể lần theo đường dẫn thực thi của mã nguồn một cách liên tục. Tuy nhiên, để tránh làm bài viết trở nên quá dài dòng, chúng ta cần giới hạn phạm vi bàn luận. Mục tiêu chính của bài viết này là hướng dẫn người đọc bắt đầu từ hàm main và lần lượt đi sâu vào mã nguồn cho đến khi đạt đến điểm nhập khẩu của bất kỳ lệnh Redis nào. Khi đã hiểu rõ được hành trình này, người đọc sẽ có cái nhìn tổng quan hơn về cách thức hoạt động bên trong hệ thống Redis, từ đó dễ dàng tiếp cận với các phân tích sâu hơn phía trước. Phân tích cấu trúc dữ liệu nội bộ của Redis Một loạt bài viết này đã nối tiếp nhau. Hoặctỷ lệ kèo bóng đá trực tiếp, bạn cũng có thể tự mình khám phá phần còn lại.
Để diễn đạt rõ ràngtai ban ca, bài viết này sẽ tiến hành theo ý tưởng sau đây:
Dựa trên việc phân chia nàytỷ lệ kèo bóng đá trực tiếp, nếu bạn chỉ muốn đọc lướt qua để nắm được quy trình xử lý chính, thì chỉ cần đọc hai phần đầu là đủ. Hai phần sau sẽ đi sâu vào những chi tiết quan trọng mà bạn nên chú ý.
Chú thích: Phân tích trong bài viết này dựa trên nhánh mã nguồn 5.0 của Redis.
Hàm main của Redis được tìm thấy trong tệp nguồn server.c. Sau khi hàm main bắt đầu thực thitai ban ca, logic chính có thể được chia thành hai giai đoạn rõ rệt:
Hai giai đoạn thực thi này có thể được biểu thị bằng sơ đồ quy trình dưới đây (nhấn để xem lớn hơn):
Trước tiênkeo 88, chúng ta hãy xem xét từng bước trong giai đoạn khởi tạo:
redisServer
Biến toàn cục loại này để biểu thị (tên biến là)
server
Bước này chủ yếu tập trung vào việc khởi tạo biến toàn cục. Trong quá trình khởi tạotỷ lệ kèo bóng đá trực tiếp, có một hàm cần đặc biệt chú ý:
populateCommandTable
Nó khởi tạo bảng lệnh Redistỷ lệ kèo bóng đá trực tiếp, qua đó có thể tìm thấy thông tin cấu hình của bất kỳ lệnh Redis nào chỉ bằng tên của lệnh đó (ví dụ như số lượng tham số lệnh mà lệnh đó nhận, hàm thực thi được liên kết với nó, v.v.). Trong phần hai của bài viết này, chúng ta sẽ cùng nhau khám phá cách một yêu cầu Redis bắt đầu từ việc nhận lệnh, đi qua từng bước thực hiện, cho đến khi tra cứu bảng lệnh này để xác định nơi bắt đầu thực thi lệnh. Ngoài ra, ở bước này còn có một điểm đáng chú ý: trong quá trình xử lý toàn cục của...
redisServer
Sau khi cấu trúc được khởi tạokeo 88, bạn cần tải các cấu hình từ tệp cấu hình (redis.conf). Quá trình này có thể ghi đè lên các thiết lập mà bạn đã đặt trước đó trong quá trình khởi tạo. Điều quan trọng là phải kiểm tra kỹ lưỡng các giá trị hiện tại để đảm bảo không có sự nhầm lẫn nào xảy ra giữa những thiết lập cũ và mới.
redisServer
Trong cấu trúc nàytỷ lệ kèo bóng đá trực tiếp, một số tham số cụ thể sẽ được xử lý theo cách sau: trước tiên, quá trình khởi tạo ban đầu sẽ diễn ra để đảm bảo rằng các cấu trúc dữ liệu nội bộ cũng như các tham số của Redis đều có giá trị mặc định. Sau đó, hệ thống sẽ tiếp tục tải các cấu hình tùy chỉnh từ tệp cấu hình để điều chỉnh những thông số cần thiết theo yêu cầu cụ thể.
aeEventLoop
tạo vòng lặp sự kiện
aeEventLoop
Kết cấu và lưu trữ vào
server
Biến toàn cục (tức là biến mà chúng ta đã đề cập trước đó)
redisServer
Bạn có thể thực hiện việc này trong cấu trúc của loại (type structure). Ngoài ratai ban ca, vòng lặp sự kiện phụ thuộc vào cơ chế đa hóa nhập xuất (I/O multiplexing) ở tầng nền hệ thống, chẳng hạn như trên hệ điều hành Linux: Cụ thể hơn, khi một ứng dụng chạy trên môi trường Linux, vòng lặp sự kiện sẽ tận dụng các công cụ như hệ thống gọi `select()`, `poll()` hoặc `epoll()` để theo dõi trạng thái của nhiều luồng I/O cùng một lúc. Điều này giúp tối ưu hóa hiệu suất bằng cách giảm thiểu thời gian chờ đợi cho các hoạt động nhập/xuất. Hệ thống I/O đa luồng này không chỉ tăng cường khả năng xử lý đồng thời mà còn làm giảm tải cho CPU khi quản lý nhiều nguồn dữ liệu khác nhau. Hơn nữa, nhờ vào cơ chế này, các tác vụ lớn như truyền tải tệp, kết nối mạng hay tương tác với thiết bị ngoại vi đều có thể được thực hiện hiệu quả mà không làm chậm tốc độ phản hồi của chương trình. Đây là một trong những yếu tố quan trọng giúp lập trình hướng sự kiện trở nên mạnh mẽ và linh hoạt trong các hệ thống hiện đại.
Cơ chế epoll
[1]. Do đótỷ lệ kèo bóng đá trực tiếp, bước này cũng bao gồm việc khởi tạo cơ chế đa luồng I/O phía dưới (gọi API hệ thống).
server
Trong phạm vi biến toàn cụctỷ lệ kèo bóng đá trực tiếp, đối với việc lắng nghe giao thức TCP, vì địa chỉ IP và cổng có thể được kết nối với nhiều luồng khác nhau, nên mô tả tệp (file descriptor) dùng để lắng nghe các kết nối TCP cũng có thể chứa nhiều giá trị. Sau đó, chương trình sẽ sử dụng các mô tả tệp này để đăng ký các sự kiện I/O và thiết lập hàm gọi trả về tương ứng. Điều này giúp chương trình dễ dàng xử lý các yêu cầu từ nhiều nguồn khác nhau một cách linh hoạt và hiệu quả.
serverCron
Do Redis chỉ có một luồng chínhtai ban ca, hàm này sẽ được thực hiện định kỳ ngay trong chính luồng đó. Nó được kích hoạt bởi vòng lặp sự kiện (tức được gọi vào thời điểm phù hợp), nhưng điều này không làm gián đoạn việc thực thi các logic khác cùng trên luồng đó (giống như việc phân chia thời gian để xử lý từng tác vụ riêng biệt).
serverCron
Bạn có thể tự hỏi hàm này thực sự làm gì? Thực tếtỷ lệ kèo bóng đá trực tiếp, ngoài việc định kỳ thu hồi các key đã hết hạn, nó còn đảm nhận nhiều nhiệm vụ khác như kết nối lại giữa chủ và nô lệ, tái kết nối giữa các nút trong Cluster, cũng như kích hoạt và thực thi các lệnh BGSAVE và Tuy nhiên, đây không phải là trọng tâm của bài viết này, nên mình sẽ không đi sâu vào chi tiết ở đây. Hãy tưởng tượng rằng hàm này như một người quản lý đa năng trong hệ thống, luôn hoạt động âm thầm để duy trì sự ổn định và hiệu quả. Nó không chỉ xử lý vấn đề về key mà còn liên tục giám sát và điều chỉnh các kết nối phức tạp trong mạng lưới phân tán, từ đó đảm bảo toàn bộ hệ thống vận hành trơn tru mà ít ai nhận ra.
acceptTcpHandler
và
acceptUnixHandler
Khi có yêu cầu đến từ client Redistai ban ca, quá trình xử lý sẽ đi qua hai hàm này. Trong phần tiếp theo, chúng ta sẽ tìm hiểu kỹ hơn về quy trình xử lý này. Ngoài ra, thực tế là ở đây Redis còn đăng ký một sự kiện I/O để thông qua ống (pipe), điều này giúp tối ưu hóa hiệu suất truyền dữ liệu giữa các tiến trình và đảm bảo tính đồng bộ trong việc xử lý yêu cầu từ client.
pipe
[6]) cơ chế giao tiếp hai chiều với module. Điều này không phải là trọng tâm của bài viết nàykeo 88, vì vậy chúng tôi tạm thời bỏ qua nó.
serverCron
Hàmtỷ lệ kèo bóng đá trực tiếp, theo lý thuyết, các tác vụ được thực hiện bởi luồng nền có thể cũng được đặt vào
serverCron
Để thực hiện. Vì
serverCron
Hàm cũng có thể được sử dụng để thực hiện tác vụ nền. Trên thực tếkeo 88, làm như vậy là không khả thi. Trước đó, chúng tôi đã đề cập rằng,
serverCron
Hệ thống sẽ được điều khiển bởi vòng lặp sự kiệntỷ lệ kèo bóng đá trực tiếp, và việc thực thi vẫn diễn ra trên luồng chính của Redis. Điều này có nghĩa là các tác vụ khác nhau, bao gồm cả những hoạt động khác đang chạy trên luồng chính (chủ yếu liên quan đến việc xử lý yêu cầu lệnh), sẽ được chia nhỏ theo thời gian. Với cách làm này, chúng ta có thể: Cải thiện hiệu suất tổng thể của hệ thống bằng cách đảm bảo rằng không có tác vụ nào bị giữ lại quá lâu, đồng thời duy trì khả năng đáp ứng cho các yêu cầu mới. Hơn nữa, việc chia nhỏ công việc giúp tối ưu hóa tài nguyên, giảm thiểu nguy cơ xảy ra tình trạng treo hoặc chậm trễ trong xử lý dữ liệu. Nhờ đó, Redis có thể dễ dàng quản lý nhiều yêu cầu cùng lúc mà vẫn duy trì tính ổn định và tốc độ cao.
serverCron
Bạn không nên thực hiện các tác vụ quá tốn thời gian bên trong Redistai ban ca, vì điều này có thể làm gián đoạn thời gian phản hồi khi xử lý lệnh. Do đó, đối với những nhiệm vụ tiêu tốn nhiều thời gian và có thể bị trì hoãn, cách tốt nhất là chúng cần được chuyển sang một luồng riêng biệt để thực thi. Trong thực tế, việc sử dụng một hàng đợi (queue) như Redis Pub/Sub hoặc Redis Lists có thể giúp bạn quản lý hiệu quả các tác vụ dài hạn. Bạn có thể đẩy các tác vụ này vào hàng đợi và xử lý chúng bằng một tiến trình con hoặc dịch vụ độc lập. Điều này không chỉ giúp duy trì hiệu suất của Redis mà còn cho phép bạn mở rộng hệ thống một cách linh hoạt hơn.Khi khởi động một máy chủ Redistỷ lệ kèo bóng đá trực tiếp, thực tế có rất nhiều việc cần được thực hiện, chẳng hạn như tải dữ liệu vào bộ nhớ, khởi tạo cụm Cluster, khởi tạo các mô-đun, v.v. Tuy nhiên, để đơn giản hóa, quy trình khởi động mà chúng ta vừa thảo luận chỉ liệt kê những bước mà chúng ta đang tập trung vào hiện tại. Bài viết này chủ yếu tập trung vào cơ chế vận hành được thúc đẩy bởi sự kiện và phần liên quan trực tiếp đến việc thực thi lệnh, vì vậy chúng ta tạm thời bỏ qua các bước khác ít liên quan hơn trong quá trình này. Thêm vào đó, việc quản lý tài nguyên và tối ưu hóa hiệu suất cũng là một phần không thể thiếu trong quy trình khởi động này. Điều này bao gồm việc tối ưu hóa cách lưu trữ dữ liệu, đảm bảo tính ổn định của mạng lưới Cluster, và kiểm tra tính tương thích của các mô-đun được cài đặt. Tất cả những yếu tố này góp phần tạo nên một hệ thống Redis hoạt động trơn tru và hiệu quả.
Bây giờtỷ lệ kèo bóng đá trực tiếp, chúng ta tiếp tục thảo luận về giai đoạn thứ hai trong sơ đồ quy trình ở trên: vòng lặp sự kiện.
Chúng ta hãy nghĩ xem tại sao ở đây cần một vòng lặp.
đợi sự kiện xảy ra
Trên thực tếtỷ lệ kèo bóng đá trực tiếp, cơ chế vòng lặp sự kiện này rất phổ biến và quen thuộc đối với những ai đã từng phát triển ứng dụng di động. Ví dụ như các ứng dụng chạy trên iOS hoặc Android đều có một vòng lặp tin nhắn (message loop), đảm nhiệm việc chờ đợi và xử lý các sự kiện liên quan đến giao diện người dùng (như nhấp chuột, vuốt màn hình) trước khi đưa ra phản hồi. Tương tự như vậy, khi áp dụng nguyên tắc này lên máy chủ, vòng lặp sẽ tiếp tục hoạt động, nhưng thay vì chỉ xử lý các sự kiện UI, nó sẽ tập trung vào các sự kiện đầu vào và đầu ra (I/O). Bên cạnh đó, trong quá trình vận hành hệ thống, chắc chắn sẽ cần phải thực hiện một số tác vụ dựa theo thời gian nhất định. Chẳng hạn như trì hoãn một hành động nào đó trong 100 miligiây hoặc lặp lại một tác vụ sau mỗi giây. Điều này đòi hỏi hệ thống phải có khả năng chờ đợi và xử lý các sự kiện khác biệt - đó là sự kiện thời gian (timer event). Với những yêu cầu này, vòng lặp không chỉ đơn thuần là một công cụ mà còn là nền tảng cốt lõi để duy trì hiệu suất và tính linh hoạt của cả ứng dụng di động lẫn dịch vụ máy chủ.
Các sự kiện timer và sự kiện I/O là hai loại hoàn toàn khác nhautỷ lệ kèo bóng đá trực tiếp, làm thế nào để vòng lặp sự kiện có thể điều phối chúng một cách thống nhất? Giả sử rằng khi vòng lặp sự kiện ở trạng thái rảnh rỗi, nó sẽ chờ đợi sự kiện I/O xảy ra. Tuy nhiên, nếu một sự kiện timer xảy ra trước, thì vòng lặp sự kiện sẽ không được đánh thức kịp thời (vẫn đang chờ sự kiện I/O). Ngược lại, nếu vòng lặp sự kiện đang chờ một sự kiện timer mà một sự kiện I/O xảy ra trước, thì cũng không thể được đánh thức kịp thời. Do đó, chúng ta cần một cơ chế nào đó có khả năng chờ đồng thời cả hai loại sự kiện này. May mắn thay, một số API của hệ thống có thể thực hiện được điều này (như các API mà chúng tôi đã đề cập trước đây như...). Cơ chế epoll )。
Biểu đồ quy trình ở giai đoạn thứ hai đã khá rõ ràng trong việc thể hiện quy trình thực thi vòng lặp sự kiện. Ở phần nàykeo 88, chúng ta sẽ đi sâu hơn vào một số bước quan trọng và cung cấp thêm các chú thích bổ sung để làm rõ hơn: 1. **Khởi động vòng lặp**: Đây là bước đầu tiên trong quá trình, nơi hệ thống chuẩn bị tất cả các tài nguyên cần thiết trước khi bắt đầu xử lý các sự kiện. Điều quan trọng là phải đảm bảo rằng mọi đối tượng liên quan đều được khởi tạo đúng cách. 2. **Đọc sự kiện**: Ở bước này, vòng lặp sẽ chờ đợi và thu thập các sự kiện từ các nguồn khác nhau như hệ thống, người dùng hoặc mạng. Cần lưu ý rằng việc tối ưu hóa thời gian phản hồi ở đây có thể ảnh hưởng lớn đến hiệu suất tổng thể của ứng dụng. 3. **Xử lý sự kiện**: Sau khi đọc được sự kiện, hệ thống sẽ phân tích và xác định cách thức giải quyết từng loại sự kiện. Điều này đòi hỏi phải có cơ chế linh hoạt để điều chỉnh hành vi dựa trên đặc điểm riêng của mỗi sự kiện. 4. **Cập nhật trạng thái**: Một khi sự kiện đã được xử lý, hệ thống sẽ cập nhật trạng thái của ứng dụng theo yêu cầu. Quá trình này cần được kiểm soát chặt chẽ để tránh lỗi về dữ liệu hoặc xung đột trạng thái. 5. **Render nội dung**: Cuối cùng, sau khi tất cả các thay đổi đã được áp dụng, nội dung cần được hiển thị lại cho người dùng. Điều này yêu cầu khả năng tối ưu hóa hình ảnh và hiệu suất đồ họa để đảm bảo trải nghiệm mượt mà. Hy vọng những thông tin bổ sung này sẽ giúp bạn hiểu rõ hơn về cách hoạt động của vòng lặp sự kiện trong quy trình đã đề xuất.
acceptTcpHandler
và
acceptUnixHandler
tai ban ca, chính ở bước này callback được gọi.
serverCron
Trong trường hợp nàytai ban ca, nó sẽ được kích hoạt để thực thi. Theo quy tắc thông thường, khi một sự kiện timer được xử lý xong, nó sẽ bị xóa khỏi hàng đợi và không còn được thực hiện thêm một lần nào nữa. Tuy nhiên, có những tình huống đặc biệt mà...
serverCron
Thực tếkeo 88, nó được gọi định kỳ. Nhưng tại sao lại như vậy? Điều này là do Redis có một cơ chế nhỏ trong việc xử lý các sự kiện timer: hàm callback của timer có thể trả về số mili giây cần thiết để thực hiện lần tiếp theo. Nếu giá trị trả về là một số dương hợp lệ, Redis sẽ không xóa sự kiện timer khỏi hàng đợi vòng lặp sự kiện. Điều này cho phép nó có cơ hội được thực thi thêm một lần nữa sau đó. Ví dụ, với cài đặt mặc định,
serverCron
Giá trị trả về là 100tai ban ca, do đó nó sẽ được thực hiện một lần sau mỗi 100 miligiây (tất nhiên, tần suất này có thể được cấu hình lại trong tệp redis.conf). Ngoài ra, bạn cũng có thể điều chỉnh các tham số khác trong tệp cấu hình này để tối ưu hóa hiệu suất của ứng dụng dựa trên nhu cầu cụ thể.
hz
Biến điều chỉnh).
Cho đến thời điểm nàykeo 88, chúng ta đã hiểu rõ khung cảnh chính của vòng lặp sự kiệ Quy trình xử lý chính của Redis bao gồm việc nhận yêu cầu, thực hiện lệnh và định kỳ thực hiện các tác vụ nền (background tasks) một cách hiệu quả. Ngoài ra, Redis còn được thiết kế để có thể xử lý nhiều kết nối cùng lúc, đảm bảo không bị chậm trong quá trình hoạt động, nhờ vào cơ chế quản lý tài nguyên thông minh mà nó tích hợp.
serverCron
Tất cả đều được thúc đẩy bởi vòng lặp sự kiện này. Khi có yêu cầu đếntai ban ca, sự kiện đầu vào và đầu ra (I/O) sẽ được kích hoạt, và vòng lặp sự kiện sẽ được đánh thức để thực thi lệnh dựa trên yêu cầu và trả về kết quả phản hồi; đồng thời, các tác vụ bất đồng bộ nền (như thu gom key hết hạn) sẽ được chia thành nhiều phần nhỏ hơn, được kích hoạt bởi sự kiện timer, và chạy định kỳ xen kẽ giữa các lần xử lý sự kiện I/O. Cách thực hiện này cho phép sử dụng duy nhất một luồng để xử lý hàng loạt yêu cầu và cung cấp thời gian phản hồi nhanh chóng. Tất nhiên, cách triển khai này có thể vận hành hiệu quả không chỉ nhờ cấu trúc của vòng lặp sự kiện mà còn nhờ vào cơ chế đa luồng của hệ thống (I/O multiplexing), cho phép truy cập đồng thời vào nhiều thiết bị đầu cuối hoặc nguồn dữ liệu. Vòng lặp sự kiện giúp CPU được sử dụng theo từng khoảng thời gian, nhưng không có "thực sự" sự song song trong việc thực thi các khối mã khác nhau. Tuy nhiên, cơ chế đa luồng I/O đảm bảo rằng CPU và I/O có thể thực hiện công việc cùng lúc. Ngoài ra, việc sử dụng một luồng duy nhất còn mang lại lợi ích bổ sung: tránh được việc thực thi đồng thời, do đó không cần phải lo lắng về vấn đề an toàn luồng khi truy cập các cấu trúc dữ liệu khác nhau, từ đó giảm đáng kể độ phức tạp trong việc triển khai.
đăng ký sự kiện callback I/O
acceptTcpHandler
Hoặc
acceptUnixHandler
Hai hàm callback này. Thực tếtai ban ca, cách mô tả này vẫn còn khá
Khi client Redis gửi lệnh đến serverkeo 88, thực chất có thể được phân thành hai giai đoạn:
thiết lập kết nối
acceptTcpHandler
Hoặc
acceptUnixHandler
Trong hai hàm callback này. Nói cách kháckeo 88, mỗi khi Redis nhận được một yêu cầu kết nối mới, vòng lặp sự kiện sẽ kích hoạt một sự kiện đầu vào (I/O), dẫn đến việc thực thi tới đây. Hơn nữa, Redis luôn hoạt động như một nền tảng mạnh mẽ để xử lý đồng thời nhiều kết nối, nhờ cơ chế quản lý sự kiện linh hoạt. Khi một yêu cầu kết nối xuất hiện, nó không chỉ đơn thuần là một dòng dữ liệu tĩnh mà còn là một phần của một quy trình phức tạp hơn, nơi mà các luồng dữ liệu được quản lý một cách tối ưu thông qua sự điều phối của vòng lặp sự kiện. Điều này cho phép Redis duy trì hiệu suất cao ngay cả trong những tình huống có khối lượng kết nối lớn.
acceptTcpHandler
Hoặc
acceptUnixHandler
Mã nguồn của hà
Tiếp theotai ban ca, từ góc độ lập trình socket, server nên gọi
accept
Hệ thống sử dụng API [7] để tiếp nhận yêu cầu kết nối và tạo ra một socket mới cho mỗi kết nối được thiết lập. Socket này cũng sẽ ánh xạ với một mô tả tệp (file descriptor) mới. Để có thể nhận được các lệnh từ phía client trên kết nối mớitỷ lệ kèo bóng đá trực tiếp, bước tiếp theo là cần đăng ký một callback sự kiện I/O cho file descriptor này trong vòng lặp sự kiện (event loop). Dưới đây là sơ đồ trình bày quy trình này: [Ở đây bạn có thể thêm một sơ đồ hoặc hình ảnh minh họa nếu cần thiết] Quy trình này đảm bảo rằng hệ thống có thể xử lý đồng thời nhiều kết nối và thực hiện các tác vụ khác nhau mà không bị gián đoạn.
Từ sơ đồ quy trình trênkeo 88, có thể thấy rằng kết nối mới đã đăng ký một callback sự kiện I/O, tức là
readQueryFromClient
gửi lệnhkeo 88, thực thi và phản hồi
readQueryFromClient
thực thi và phản hồi
Có một số điểm cần chú ý trong sơ đồ quy trình trên:
read
Đọc dữ liệu bằng cách gọi API hệ thống [8]. Mặc dù gọi
read
những gói dữ liệu dính nhau
populateCommandTable
Trước đókeo 88, bảng lệnh này được lưu trữ trong biến toàn cục
redisCommandTable
server.c. Trong bảng lệnh lưu trữ các cổng vào thực hiện của các lệnh Redis.
Trong phần đầu tiên của bài viếtkeo 88, chúng ta đã đề cập rằng cần có một cơ chế có khả năng chờ đợi cùng lúc hai loại sự kiện là I/O và timer. Cơ chế này chính là phương thức đa hướng hóa I/O (I/O multiplexing) nằm ở tầng cơ sở của hệ thống. Tuy nhiên, trên các nền tảng hệ điều hành khác nhau, tồn tại nhiều cách thức đa hướng hóa I/O khác biệt. Do đó, để tạo điều kiện thuận lợi cho việc triển khai ở lớp ứng dụng phía trên, Redis đã phát triển một thư viện lập trình hướng sự kiện đơn giản, cụ thể là mã nguồ c. Thư viện này đã che giấu những khác biệt trong việc xử lý sự kiện giữa các hệ thống và đồng thời thực hiện vòng lặp sự kiện mà chúng ta đã thảo luận từ trước đến nay. Bên cạnh đó, sự linh hoạt của thư viện này không chỉ dừng lại ở việc ẩn đi các đặc thù của từng hệ thống mà còn mang đến khả năng tùy chỉnh cao hơn cho các nhà phát triển. Bằng cách sử dụng phương pháp này, Redis có thể dễ dàng tích hợp với các hệ thống khác nhau mà không cần phải lo lắng quá nhiều về sự tương thích, giúp tối ưu hóa hiệu suất và tăng cường độ tin cậy trong việc quản lý các luồng dữ liệu. Điều này làm nổi bật vai trò quan trọng của ae.c trong việc xây dựng một kiến trúc phần mềm mạnh mẽ và hiệu quả
Trong việc triển khai thư viện sự kiện của Redistỷ lệ kèo bóng đá trực tiếp, hiện tại nó hỗ trợ 4 cơ chế đa luồng I/O phía dưới:
select
Hệ thống gọi
Đây có lẽ là cơ chế đa sử dụng đầu vào/đầu ra (I/O multiplexing) xuất hiện sớm nhấttỷ lệ kèo bóng đá trực tiếp, lần đầu tiên được áp dụng vào năm 1983 trong phiên bản Unix 4.2BSD [9]. Trong thời đại đó, sự ra đời của cơ chế này đã tạo ra một bước đột phá quan trọng cho hệ điều hành Unix, giúp tối ưu hóa cách thức xử lý nhiều luồng dữ liệu cùng một lúc. Điều này đặc biệt hữu ích khi các ứng dụng cần quản lý đồng thời nhiều nguồn dữ liệu mà không làm chậm hiệu suất tổng thể của hệ thống.
10
] là một phần của
POSIX
Tiêu chuẩn. Ngoài rakeo 88, còn có một tiêu chuẩn tương tự gọi là
select
Tiêu chuẩn. Miễn là hệ điều hành tuân theo tiêu chuẩn POSIXtỷ lệ kèo bóng đá trực tiếp, nó có thể hỗ trợ
poll
Hệ thống gọi sử dụng [11]tỷ lệ kèo bóng đá trực tiếp, đây là một tính năng lần đầu tiên xuất hiện vào năm 1986 trên hệ điều hành Unix phiên bản SVR3 [10]. Tính năng này cũng tuân theo các nguyên tắc cơ bản của hệ thống.
POSIX
Cơ chế nàytỷ lệ kèo bóng đá trực tiếp, vì vậy trong các hệ thống phổ biến hiện nay, hai cơ chế sự kiện I/O này thường được hỗ trợ.
select
và
poll
[1]. Epoll là một phương án hiệu quả hơn so với
select
Một cơ chế mới của việc đa multiplexing I/O đã xuất hiện lần đầu tiên trong phiên bản 2.5.44 của nhân Linux [12]. Mục đích được thiết kế ra là thay thế cho phương thức cũkeo 88, vốn đã trở nên lỗi thời và không còn đáp ứng được nhu cầu xử lý ngày càng cao của hệ thống. Cơ chế này nhằm tối ưu hóa hiệu suất bằng cách quản lý nhiều luồng dữ liệu đồng thời một cách hiệu quả hơn, giúp giảm thiểu đáng kể độ trễ và cải thiện tốc độ truyền tải thông tin trong môi trường đa nhiệm.
select
và
poll
Bạn có thể tạo ra một cơ chế nhập/xuất (I/O) hiệu quả hơn. Hãy lưu ý rằng epoll là một tính năng độc quyền của hệ điều hành Linux và không thuộc chuẩn POSIX. Với khả năng xử lý hàng loạt sự kiện một cách nhanh chóngkeo 88, epoll đã trở thành lựa chọn ưu tiên trong các ứng dụng cần tối ưu hóa hiệu suất trên nền tảng Linux. Điều này giúp giảm thiểu khối lượng công việc của lập trình viên khi quản lý nhiều kết nối đồng thời.
kqueue
Đây là một cơ chế sự kiện I/O đặc trưng trên
[13]。
kqueue
Nó được thiết kế lần đầu tiên vào năm 2000 trên nền tảng FreeBSD 4.1tỷ lệ kèo bóng đá trực tiếp, sau đó cũng đã được tích hợp vào các hệ điều hành như NetBSD, OpenBSD, DragonflyBSD và macOS [14]. Về cơ bản, nó có chức năng tương tự như epoll trong hệ thống Linux. Bên cạnh đó, sự phát triển này không chỉ mở ra cánh cửa cho nhiều khả năng mới mà còn làm nổi bật vai trò quan trọng của các công cụ hiệu suất cao trong việc tối ưu hóa hoạt động mạng trên nhiều nền tảng khác nhau. Đây thực sự là một bước tiến đáng chú ý trong thế giới phần mềm nguồn mở.Vì mỗi hệ thống có cơ chế sự kiện khác nhautai ban ca, vậy Redis sử dụng cơ chế nào khi biên dịch trên các hệ thống này? Trong bốn cơ chế được đề cập, ba cơ chế sau là hiện đại hơn và cũng hiệu quả hơn so với cơ chế đầu tiên. Tuy nhiên, Redis đã khéo léo chọn một cơ chế phù hợp để đảm bảo tính tương thích và hiệu suất tối ưu trên tất cả các nền tảng. Điều này cho phép Redis hoạt động ổn định không chỉ trên các hệ điều hành phổ biến như Linux hay macOS mà còn mở rộng sang các môi trường khác như Windows thông qua các bản port chính thức. Chính sự linh hoạt trong việc lựa chọn cơ chế này đã giúp Redis trở thành một trong những công cụ lưu trữ dữ liệu và xử lý luồng dữ liệu hàng đầu trong lĩnh vực công nghệ hiện nay.
select
và
poll
; Nếu biên dịch trên Linux thì sẽ chọn
Dựa trên phần tóm tắt ở trên về các cơ chế I/O được áp dụng cho các hệ điều hành khác nhautai ban ca, chúng ta có thể dễ dàng nhận ra rằng nếu bạn biên dịch Redis trên macOS, nền tảng bên dưới sẽ chọn sử dụng **kqueue**, một phương pháp giám sát sự kiện được tối ưu hóa đặc biệt cho hệ điều hành này. Điều này giúp cải thiện hiệu suất và giảm thiểu tài nguyên khi xử lý nhiều kết nối đồng thời. macOS tận dụng những tính năng tiên tiến của kqueue để tạo ra một môi trường ổn định và hiệu quả, phù hợp với nhu cầu ngày càng cao của người dùng hiện đại.
kqueue
tai ban ca, đây cũng là tình huống phổ biến trong việc chạy thực tế của Redis.
epoll
Vấn đề C10K
Điều cần lưu ý là cơ chế sự kiện đầu vào/đầu ra (I/O) mà chúng ta đang đề cập đến có mối liên hệ chặt chẽ với việc triển khai các dịch vụ mạng có khả năng xử lý nhiều kết nối cùng lúc. Nhiều bạn kỹ thuật chắc hẳn đã từng nghe nói về vấn đề này. Cơ chế I/O này đóng vai trò quan trọng trong việc tối ưu hóa hiệu suất của hệ thốngkeo 88, đặc biệt khi phải đối mặt với số lượng lớn yêu cầu đồng thời từ người dùng. Nó giúp giải quyết thách thức trong việc quản lý tài nguyên và đảm bảo rằng mỗi yêu cầu được xử lý một cách nhanh chóng và hiệu quả. Các lập trình viên thường tìm hiểu về mô hình sự kiện như Reactor hoặc Proactor để xây dựng các ứng dụng mạng mạnh mẽ. Mỗi mô hình có ưu nhược điểm riêng, tùy thuộc vào yêu cầu cụ thể của dự án mà các kỹ sư sẽ chọn giải pháp phù hợp nhất. Ví dụ, nếu bạn đang phát triển một hệ thống trò chuyện thời gian thực hoặc một nền tảng game trực tuyến, thì việc hiểu rõ và áp dụng đúng cơ chế I/O sẽ mang lại lợi thế cạnh tranh đáng kể so với các đối thủ khác. Chính vì vậy, việc nghiên cứu sâu về chủ đề này luôn được khuyến khích đối với những ai muốn làm việc trong lĩnh vực công nghệ hiện đại. Mã nguồn. Với sự phát triển không ngừng của phần cứng và mạngtai ban ca, việc một máy chủ đơn lẻ có thể duy trì tới 10.000 kết nối, thậm chí là hàng triệu kết nối, đã trở nên khả thi hơn bao giờ hết. Những vấn đề liên quan đến lập trình mạng hiệu suất cao luôn gắn bó chặt chẽ với các cơ chế nền tảng này. Dưới đây là một số bài viết blog mà bạn có thể tham khảo nếu cảm thấy hứng thú (liên kết đầy đủ có thể được tìm thấy trong phần tài liệu tham khảo ở cuối bài). Đây là những nguồn thông tin quý giá để hiểu rõ hơn về cách tối ưu hóa hiệu suất mạng và khám phá tiềm năng thực sự của hệ thống hiện đại. Hãy dành thời gian tìm hiểu để mở rộng kiến thức của mình nhé!
Bây giờ chúng ta hãy quay lại và tìm hiểu sâu hơn về cách các cơ chế sự kiện I/O ở tầng dưới cùng hỗ trợ cho vòng lặp sự kiện của Redis (mô tả dưới đây là chi tiết hóa quy trình vòng lặp sự kiện được đề cập trong phần đầu tiên của bài viết trước đó).
aeCreateFileEvent
Mọi cơ chế sự kiện phía dưới đều sẽ cung cấp một hoạt động chờ sự kiệntỷ lệ kèo bóng đá trực tiếp, chẳng hạn như epoll cung cấp
aeCreateTimeEvent
Mọi cơ chế sự kiện phía dưới đều sẽ cung cấp một hoạt động chờ sự kiệntỷ lệ kèo bóng đá trực tiếp, chẳng hạn như epoll cung cấp
epoll_wait
API này cho phép thực hiện một thao tác chờ đợitai ban ca, trong đó bạn có thể chỉ định danh sách các sự kiện mong đợi (các sự kiện được biểu diễn bằng mô tả file descriptor) và đồng thời cũng có thể đặt một khoảng thời gian tối đa để chờ đợi. Khi vòng lặp sự kiện cần chờ sự xuất hiện của các sự kiện, bạn sẽ gọi thao tác chờ này, truyền vào tất cả các sự kiện I/O đã đăng ký trước đó và chuyển đổi thời điểm của sự kiện timer gần nhất thành khoảng thời gian chờ tối đa cần thiết. Để hiểu rõ hơn về cách hoạt động, hãy tham khảo hàm cụ thể bên dưới.
aeProcessEvents
Mọi cơ chế sự kiện phía dưới đều sẽ cung cấp một hoạt động chờ sự kiệntỷ lệ kèo bóng đá trực tiếp, chẳng hạn như epoll cung cấpCuối cùngtỷ lệ kèo bóng đá trực tiếp, về cơ chế sự kiện, vẫn còn một số thông tin đáng chú ý: ngành công nghiệp đã có một số thư viện sự kiện nguồn mở tương đối chín muồi. Một ví dụ điển hình là... libevent Lý do đại khái có thể tóm tắt như sau: libev [21]. Nói chungtai ban ca, các thư viện mã nguồn mở này đã che giấu đi những chi tiết hệ thống phức tạp ở tầng và thực hiện tương thích với nhiều phiên bản hệ thống khác nhau, mang lại giá trị lớn. Vậy tại sao tác giả của Redis vẫn tự viết một hệ thống riêng? Trong một bài đăng trên Google Group, tác giả của Redis đã giải thích một số lý do. Đường dẫn bài đăng như sau:
Không muốn phụ thuộc bên ngoài quá lớn. Chẳng hạn
Về các quy trình xử lý mã nguồn đã được phân tích ở phần trướctỷ lệ kèo bóng đá trực tiếp, bao gồm khởi tạo, vòng lặp sự kiện, nhận yêu cầu lệnh, thực thi lệnh và trả về kết quả phản hồi, để giúp người đọc dễ dàng tra cứu hơn, dưới đây chúng tôi sẽ minh họa mối quan hệ gọi hàm của một số hàm quan trọng bằng biểu đồ cây (biểu đồ có kích thước lớn, nhấp chuột vào để xem ảnh lớn). Một lần nữa xin nhắc lại rằng: biểu đồ mối quan hệ gọi hàm này dựa trên mã nguồn Redis nhánh 5.0, và trong tương lai nó có thể thay đổi khi mã nguồn của Redis được cập nhật và phát triển. Biểu đồ này không chỉ giúp bạn hiểu rõ hơn về cách thức hoạt động mà còn cung cấp cái nhìn sâu sắc về cấu trúc tổng thể của Redis trong phiên bản hiện tại. Hãy nhớ rằng, việc nghiên cứu mã nguồn luôn là một hành trình thú vị nhưng cũng đầy thử thách, đặc biệt đối với những ai mới bắt đầu tìm hiểu về hệ thống cơ sở dữ liệu phân tán như Redis.
Mỗi lần nhánh sang phải của câytỷ lệ kèo bóng đá trực tiếp, biểu thị mức sâu của việc gọi hàm tăng lên một tầng (đưa vào ngăn xếp gọi hàm).
Trong hình trên đã thêm một số chú thíchtỷ lệ kèo bóng đá trực tiếp, giúp dễ dàng đối chiếu với các quy trình đã được giới thiệu trước đó trong bài viết. Ngoài ra, một số chi tiết quan trọng cần lưu ý trong hình cũng được liệt kê bên dưới: 1. Điểm đầu tiên cần đặc biệt chú ý là vị trí mà các yếu tố chính giao thoa với nhau, điều này có thể ảnh hưởng trực tiếp đến kết quả cuối cùng. 2. Tiếp theo là cách sắp xếp thứ tự các bước, cần đảm bảo tuân thủ đúng thứ tự để tránh những sai sót không đáng có. 3. Một yếu tố khác không kém phần quan trọng là sự tương tác giữa các thành phần trong biểu đồ, cần phải hiểu rõ mối liên hệ giữa chúng để có cái nhìn toàn diện hơn. Hãy dành thời gian nghiên cứu kỹ lưỡng từng chi tiết để nắm bắt đầy đủ thông tin mà hình vẽ muốn truyền tải.
aeSetBeforeSleepProc
và
aeSetAfterSleepProc
Bạn đã đăng ký hai hàm trả về (callback) mà trong phần đầu bài viết chưa được đề cập đến. Một hàm sẽ được gọi vào thời điểm bắt đầu mỗi vòng lặp sự kiệntỷ lệ kèo bóng đá trực tiếp, và hàm còn lại sẽ được thực thi sau khi mỗi vòng lặp sự kiện kết thúc việc chờ đợi tuần tự (tức là...).
aeApiPoll
, chính từ đây
beforeSleep
Đăng ký vào vòng lặp sự kiện.
aeSetBeforeSleepProc
Sự kiện định kỳ được đề cập trước đó
serverCron
Nhánh gọi này, hàm
processTimeEvents
Được gọi.
timeProc
Trong quy trình xử lý dữ liệu nhậntỷ lệ kèo bóng đá trực tiếp,
readQueryFromClient
Để kiểm tra bảng lệnh Redistai ban ca, bảng lệnh này cũng chính là bảng lệnh được khởi tạo trong
lookupCommand
Quá trình khởi tạo ban đầu
populateCommandTable
Kết cấu toàn cục. Sau khi tìm thấy cổng vào lệnhtỷ lệ kèo bóng đá trực tiếp, gọi hà c
redisCommandTable
Để thực hiện lệnh. Trong hìnhtỷ lệ kèo bóng đá trực tiếp,
call
Hàm ở tầng tiếp theotỷ lệ kèo bóng đá trực tiếp, chính là gọi hàm cổng vào của từng lệnh (trong hình chỉ liệt kê vài ví dụ). Ví dụ,
call
Hàm cổng vào lệnh
get
tai ban ca, kết quả thực thi của nó cuối cùng sẽ gọi
getCommand
Lưu vào buffer đầu ratỷ lệ kèo bóng đá trực tiếp, tức là
addReply
Kết cấu
client
Quy trình.
buf
Hoặc
reply
quy trình xử lý yêu cầu Redis
beforeSleep
và
sendReplyToClient
Cuối cùngtai ban ca, quy trình gửi kết quả thực thi lệnh đến client được thực hiện bởi
beforeSleep
Bạn có thể kích hoạt điều này. Hệ thống sẽ kiểm tra xem trong bộ đệm output có bất kỳ dữ liệu kết quả thực thi nào cần gửi cho khách hàng không. Nếu phát hiện cótai ban ca, nó sẽ tiến hành gọi đến chức năng tương ứng.
writeToClient
Bạn có thể thử gửi dữ liệu. Nếu lần đầu tiên không thể hoàn tất việc truyền tải toàn bộ dữ liệutai ban ca, bạn sẽ cần đăng ký lại một sự kiện I/O viết trong vòng lặp sự kiện. Trong trường hợp này, điều quan trọng là phải kiểm tra tình trạng kết nối và đảm bảo rằng bộ đệm dữ liệu đã sẵn sàng trước khi kích hoạt lại sự kiện I/O. Điều này giúp tối ưu hóa hiệu suất và tránh các lỗi không mong muốn. Đồng thời, bạn cũng nên lưu ý quản lý tài nguyên một cách cẩn thận để không làm quá tải hệ thống trong quá trình chờ đợi sự kiện tiếp theo xảy ra.
sendReplyToClient
Để cố gắng gửi. Nếu vẫn còn dữ liệu chưa gửi hoàn toànkeo 88, thì sau đó sẽ được kích hoạt lại quy trình này bởi
writeToClient
Callback.
beforeSleep
Tóm tắt đơn giảnkeo 88, bài viết này hệ thống ghi lại các quy trình thực thi sau đây:Quy trình khởi tạo sau khi bắt đầu từ hàm main;
Để hiểu rõ mã nguồn Redis một cách suôn sẻtỷ lệ kèo bóng đá trực tiếp, bạn cần có kinh nghiệm lập trình C trong môi trường Linux và nắm vững một số kiến thức về hệ thống Linux. Đối với nhiều người, những điều này có thể trở thành rào cản. Do đó, bài viết này ghi lại hệ thống các bước và giải pháp mà tác giả đã thực hiện trong quá trình đọc mã nguồn, đồng thời phân tích các vấn đề khó khăn quan trọng mà tác giả gặp phải. Ngoài ra, bài viết cũng đưa ra một số tài liệu tham khảo để giúp những người muốn tìm hiểu mã nguồn Redis nhưng chưa biết bắt đầu từ đâu có thể có được hướng đi phù hợp hơn. Mong rằng những chia sẻ này sẽ mang lại ít nhiều giá trị cho các bạn đang trên con đường học hỏi.
Tương tự như những gì loạt bài viết này đã làm.
redisCommandTable
Nó được định nghĩa ở phần đầu của tệp nguồn server.c. Tệp này lưu giữ điểm vào cho việc thực thi mỗi lệnh Rediskeo 88, và bạn có thể bắt đầu từ đây để tìm hiểu sâu về các cấu trúc dữ liệu cũng như các hoạt động liên quan bê Bạn sẽ thấy rằng nó giống như một bản đồ dẫn đường, mở ra cánh cửa để khám phá toàn bộ hệ thống phức tạp mà Redis đang vận hành.
Phân tích cấu trúc dữ liệu nội bộ của Redis
Chúc bạn đọc mã nguồn vui vẻ!
(Kết thúc)
Các bài viết được chọn lọc khác :