Kể từ khi tôi viết bài Phân tích cấu trúc dữ liệu bên trong Redis Sau khi loạt bài viết trước được đăng tải789 Club, không ít độc giả đã đến để đọc và thảo luận. Trong số đó, có người cũng đặt ra câu hỏi về cách đọc mã nguồn Redis. Trong bài viết này, chúng ta sẽ tập trung vào chủ đề này: Nếu bạn hiện tại muốn đọc mã nguồn Redis, thì nên bắt đầu từ đâu? Đây cũng coi như là một phần bổ sung cho loạt bài viết trước đây của chúng tôi. Trước tiên, điều quan trọng là phải hiểu rằng Redis là một hệ thống cơ sở dữ liệu phân tán, được viết hoàn toàn bằng ngôn ngữ C. Điều này có nghĩa là để đọc và hiểu mã nguồn của nó, bạn cần có một kiến thức nhất định về lập trình C cũng như cách hoạt động của các hệ thống lưu trữ. Việc hiểu rõ cấu trúc cơ bản của Redis, chẳng hạn như các luồng xử lý chính hoặc cách các lệnh được thực thi, sẽ giúp bạn nhanh chóng nắm bắt được cốt lõi của mã nguồn. Một gợi ý tốt để bắt đầu là tìm hiểu về file `redis.c`, đây là điểm khởi đầu chính trong mã nguồn của Redis. File này chứa các hàm chính như `main()` và các cấu trúc dữ liệu quan trọng mà Redis sử dụng để quản lý trạng thái của ứng dụng. Khi đọc file này, bạn sẽ thấy được cách Redis thiết lập và bắt đầu hoạt động. Bên cạnh đó, việc tìm hiểu kỹ các module cụ thể cũng rất hữu ích. Redis có nhiều module mở rộng (module extension), chẳng hạn như Redis Modules API, cho phép người dùng tùy chỉnh và thêm các tính năng mới vào hệ thống. Việc nghiên cứu những module này sẽ giúp bạn hiểu sâu hơn về khả năng mở rộng của Redis và cách nó hoạt động trong môi trường thực tế. Cuối cùng, đừng quên tham khảo tài liệu chính thức của Redis và các diễn đàn trực tuyến nơi các lập trình viên khác chia sẻ kinh nghiệm đọc mã nguồn. Điều này sẽ giúp bạn tiếp cận với nhiều góc nhìn khác nhau và rút ngắn thời gian học hỏi. Hy vọng với những thông tin trên, bạn sẽ có cái nhìn rõ ràng hơn về cách bắt đầu đọc mã nguồn Redis và có một hành trình khám phá thú vị!
Redis được viết bằng ngôn ngữ Ci9bet.com nhận 100k, và khi bắt đầu tìm hiểu mã nguồn, tất nhiên bạn nên bắt đầu từ hàm main. Tuy nhiên, trong quá trình đọc, chúng ta cần bám sát một dòng chảy chính: đó là khi chúng ta nhập một lệnh vào Redis, mã nguồn sẽ thực thi theo từng bước như thế nào. Đầu tiên, chúng ta có thể quan sát từ bên ngoài, thử chạy một số lệnh để xem phản ứng của hệ thống ra sao. Sau khi đã nắm được cách hoạt động bên ngoài của các lệnh này, chúng ta mới đi sâu vào tìm hiểu cách mà mã nguồn thực hiện chúng. Để hiểu được những đoạn mã này, trước hết chúng ta cần hiểu rõ cơ chế sự kiện (event mechanism) của Redis. Và khi đã hiểu rõ cơ chế vòng lặp sự kiện (event loop) của Redis, chúng ta sẽ giải được một câu hỏi thú vị: Tại sao Redis lại chạy theo mô hình đơn luồng (single-threaded) mà vẫn có thể xử lý đồng thời nhiều yêu cầu? (Tất nhiên, nếu xét kỹ hơn, không phải Redis chỉ có duy nhất một luồng; nhưng các luồng khác ngoài luồng chính chỉ đóng vai trò hỗ trợ, chẳng hạn như chạy nền để xử lý các tác vụ tốn thời gian một cách đồng bộ). Chúng ta cũng cần lưu ý rằng các luồng phụ của Redis không phải là yếu tố quyết định cho khả năng xử lý đồng thời của nó. Thay vào đó, chính vòng lặp sự kiện và cách Redis quản lý các kết nối mạng đã tạo nên hiệu suất cao và khả năng phục vụ nhiều yêu cầu cùng lúc. Điều này cũng giúp giải thích tại sao Redis có thể đạt được tốc độ xử lý nhanh chóng mà không cần đến nhiều tài nguyên máy tính phức tạp.
Từ hàm maini9bet.com nhận 100k, chúng ta có thể tiếp tục lần theo con đường thực thi của mã nguồn một cách liên tục. Tuy nhiên, để tránh việc bài viết trở nên quá dài dòng và khó hiểu, chúng ta cần xác định rõ phạm vi. 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, đi từng bước một để lần theo đường dẫn thực thi, cuối cùng đạt đến điểm nhập khẩu của bất kỳ lệnh Redis nào. Khi đã hoàn thành hành trình này, chúng ta sẽ có thể... Phân tích cấu trúc dữ liệu bên trong Redis Một loạt các bài viết này đã nối tiếp nhau. Hoặci9bet.com nhận 100k, bạn cũng có thể tự mình hoàn thành việc khám phá còn lại.
Để diễn đạt rõ ràngbầu cua, bài viết này sẽ tiến hành theo cách nghĩ như sau:
Dựa trên việc phân chia nàyi9bet.com nhận 100k, 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 tiên 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ú ý.
Lưu ý: 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 thibầu cua, logic tổng thể có thể được chia thành hai giai đoạn chính: 1. **Giai đoạn khởi tạo hệ thống**: Trong giai đoạn này, các cấu hình ban đầu sẽ được thiết lập, bao gồm việc đọc các tham số từ dòng lệnh, kiểm tra cấu hình và thiết lập các thông số cần thiết cho hoạt động của Redis. Đồng thời, cơ sở dữ liệu sẽ được khởi tạo và chuẩn bị sẵn sàng để nhận yêu cầu từ người dùng hoặc client. 2. **Giai đoạn chạy liên tục (event loop)**: Sau khi hoàn tất quá trình khởi tạo, chương trình sẽ chuyển sang trạng thái lắng nghe và xử lý các yêu cầu từ client. Redis sử dụng một vòng lặp sự kiện để theo dõi các kết nối mạng, phân tích yêu cầu và trả về phản hồi một cách nhanh chóng. Đây là phần cốt lõi giúp Redis duy trì hiệu suất cao trong việc quản lý và xử lý dữ liệu.
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 vào để xem hình lớn):
Đầu tiênbầu cua, chúng ta cùng xem qua từng bước trong giai đoạn khởi tạo:
redisServer
Các biến toàn cục kiểu này được biểu thị bằng cách đặt 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ạobầu cua, có một hàm cần đặc biệt chú ý:
populateCommandTable
Nó khởi tạo bảng lệnh Redis789 Club, 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 lệnh (ví dụ như số lượng tham số mà lệnh nhận được, địa chỉ hàm thực thi, 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 lệnh Redis được xử lý từ khi nhận được yêu cầu ban đầu cho đến khi tìm ra điểm vào thực thi thông qua việc tra cứu bảng lệnh này. 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ạobầu cua, bạn vẫ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 giá trị mà bạn đã thiết lập trong quá trình khởi tạo trước đó. Ngoài ra, việc kiểm tra thêm các tùy chọn nâng cao trong tệp cấu hình cũng giúp tối ưu hóa hiệu suất và đảm bảo rằng mọi cài đặt đều phù hợp với yêu cầu cụ thể của hệ thống.
redisServer
Trong cấu trúc nàyi9bet.com nhận 100k, một số tham số cụ thể sẽ được xử lý trước khi chạy. Nói cách khác, quá trình đầu tiên là khởi tạo mặc định để đả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 trước. Sau đó, hệ thống sẽ tải xuống các thiết lập tùy chỉnh từ tập tin cấu hình để thay thế những giá trị mặc định này nếu cần thiết.
aeEventLoop
Tạo vòng lặp sự kiện
aeEventLoop
Kết cấu và lưu trữ nó vào
server
Biến toàn cục (tức là biến được đề cập trước đó)
redisServer
Bạn có thể thực hiện các tác vụ trong cấu trúc của loại này. Ngoài rai9bet.com nhận 100k, vòng lặp sự kiện phụ thuộc vào cơ chế đa hóa đầu vào/đầu ra (I/O multiplexing) ở tầng hệ thống cơ bản, chẳng hạn như trên hệ điều hành Linux, nơi mà: Trong môi trường này, việc quản lý nhiều luồng hoạt động I/O được thực hiện một cách hiệu quả thông qua các công cụ như epoll(), giúp tối ưu hóa hiệu suất bằng cách theo dõi đồng thời nhiều luồng dữ liệu. Điều này cho phép hệ thống xử lý nhiều yêu cầu cùng một lúc mà không cần chờ đợi từng hoạt động I/O hoàn thành riêng lẻ, từ đó tăng cường khả năng ứng phó với các yêu cầu phức tạp và cải thiện tốc độ phản hồi.
Cơ chế epoll
[1]. Do đóbầu cua, 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ụcbầu cua, đối với việc lắng nghe kết nối TCP, vì địa chỉ IP và cổng có thể được gán cho nhiều luồng khác nhau, nên tệp mô tả file (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 file descriptor này để đăng ký các sự kiện I/O và thiết lập callback tương ứng. Điều này cho phép chương trình theo dõi và xử lý các hoạt động liên quan đến mạng một cách hiệu quả hơn.
serverCron
Do Redis chỉ có một luồng chính789 Club, hàm này sẽ được thực hiện định kỳ ngay trong luồng đó. Nó được điều khiển bởi vòng lặp sự kiện (tức được gọi vào thời điểm thích hợp), nhưng không ảnh hưởng đến việc thực thi các logic khác trên cùng một luồng (giống như việc phân chia thời gian cho từng tác vụ). Điều này giúp duy trì hiệu quả và không làm gián đoạn hoạt động của các phần khác trong hệ thống.
serverCron
Bạn có thể tự hỏi hàm này thực sự làm gì? Về cơ bản789 Club, ngoài việc định kỳ dọn dẹp các key đã hết hạn, nó còn thực hiện rất nhiều tác vụ khác. Chẳng hạn như tái kết nối giữa master và slave, tái đồng bộ giữa các nút trong Cluster, kích hoạt quá trình BGSAVE và rewrite của AOF, v.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. Điều đáng chú ý là mỗi nhiệm vụ này đều đóng vai trò quan trọng trong việc duy trì sự ổn định và hiệu quả của hệ thống. Ví dụ như việc tái kết nối master-slave đảm bảo dữ liệu luôn được sao lưu an toàn, trong khi đó các thao tác trên AOF giúp lưu giữ nhật ký giao dịch một cách chính xác. Tất cả những điều này cùng nhau tạo nên một hệ thống bền vững và đáng tin cậy.
acceptTcpHandler
Một quá trình đọc: Quá trình thứ
acceptUnixHandler
Khi có yêu cầu đến từ client Redis789 Club, quy trình xử lý sẽ đi qua hai hàm này. Trong phần tiếp theo, chúng ta sẽ thảo luận chi tiết về quy trình xử lý đó. Bên cạnh đó, thực tế là tại đây Redis còn đăng ký một sự kiện đầu vào/đầu ra (I/O), với mục đích sử dụng pipeline để... Đây là một cách tiếp cận hiệu quả giúp tối ưu hóa luồng dữ liệu giữa client và server, đồng thời đảm bảo rằng các giao dịch được thực hiện nhanh chóng và ổn định mà không gây ra tình trạng nghẽn mạng hoặc mất dữ liệu trong quá trình truyền tải.
pipe
Khởi tạo luồng nền
serverCron
để thực hiện. Vì
serverCron
Hàm cũng có thể được sử dụng để thực hiện nhiệm vụ nền. Trên thực tế789 Club, điều này không khả thi. Trước đây chúng tôi đã đề cập rằng,
serverCron
Khởi động vòng lặp sự kiện
serverCron
Hệ thống được điều khiển bởi vòng lặp sự kiệni9bet.com nhận 100k, 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ả các yêu cầu xử lý lệnh (chủ yếu liên quan đến việc thực hiện các yêu cầu lệnh), sẽ được phân chia theo thời gian trên cùng một luồng chính. Cách làm này giúp: - Giảm thiểu tình trạng xung đột tài nguyên giữa các tác vụ. - Tăng cường hiệu quả trong việc quản lý các yêu cầu đồng thời. - Đảm bảo rằng không có tác vụ nào bị trì hoãn quá lâu so với lịch trình dự kiến. Từ đó, hệ thống có thể hoạt động mượt mà hơn và đáp ứng tốt hơn nhu cầu của người dùng.
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 Redisbầu cua, vì điều này có thể làm chậm phản hồi của các lệnh được thực thi. 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 chạy trên một luồng riêng biệt để tránh ảnh hưởng đến hiệu suất chung của Redis. Ngoài ra, việc tách biệt các tác vụ nặng nhọc sang một luồng riêng biệt cũng giúp hệ thống duy trì sự ổn định và tăng cường khả năng xử lý đồng thời cho các yêu cầu khác nhau. Điều này đặc biệt quan trọng khi bạn đang quản lý một cơ sở dữ liệu lớn hoặc một ứng dụng có khối lượng công việc cao.Lưu ý rằng việc khởi động một máy chủ Redis thực sự còn phải thực hiện rất nhiều công việc khácbầu cua, 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 module, 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 hiện tại đang quan tâm. Bài viết này tập trung vào toàn bộ cơ chế vận hành được thúc đẩy bởi sự kiện cũng như các phần trực tiếp liên quan đến việc thực thi lệnh, do đó chúng ta tạm thời bỏ qua những bước khác ít liên quan hơn. Điều thú vị là quá trình tải dữ liệu vào bộ nhớ không chỉ giúp tăng tốc độ truy xuất thông tin mà còn tạo nền tảng cho khả năng xử lý nhanh chóng của Redis. Đồng thời, việc cấu hình và khởi chạy các module cũng đóng vai trò quan trọng trong việc mở rộng tính năng của hệ thống. Tất cả những yếu tố này sẽ được tìm hiểu sâu hơn ở các bài viết sau.
Chúng ta hãy suy nghĩ tại sao ở đây cần một vòng lặp.
Tìm kiếm sự kiện timer gần nhất
đợi sự kiện xảy ra
Trên thực tế789 Club, cơ chế vòng lặp sự kiện này rất quen thuộc và cơ bản đố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, chịu trách nhiệm chờ đợi các sự kiện giao diện người dùng (như nhấp chuột, vuốt chạm) xảy ra rồi xử lý chúng. Tương tự như vậy, khi áp dụng lên phía máy chủ, nguyên tắc hoạt động của vòng lặp cũng khá tương đồng, chỉ khác là thay vì đợi và xử lý các sự kiện UI thì nó sẽ đợi và xử lý các sự kiện I/O (như đọc ghi dữ liệu). Ngoài ra, trong quá trình hệ thống vận hành, chắc chắn sẽ cần phải sắp xếp và thực hiện một số tác vụ theo thời gian, chẳng hạn như thực hiện một thao tác sau 100 miligiây hoặc lặp lại một tác vụ định kỳ cứ mỗi giây một lần. Điều này đòi hỏi việc chờ đợi và xử lý một loại sự kiện khác, đó chính là sự kiện timer. Bên cạnh đó, một yếu tố quan trọng mà không thể bỏ qua trong vòng lặp sự kiện là khả năng cân bằng giữa hiệu suất và tài nguyên. Khi các sự kiện I/O hoặc timer được kích hoạt, hệ thống cần đảm bảo rằng chúng sẽ không bị bỏ sót hay xử lý chậm trễ. Điều này đặc biệt quan trọng khi bạn đang làm việc với một ứng dụng có yêu cầu cao về độ ổn định và tốc độ phản hồi, chẳng hạn như trò chơi trực tuyến hoặc nền tảng thương mại điện tử. Do đó, việc tối ưu hóa vòng lặp sự kiện trở thành một phần không thể thiếu trong quy trình phát triển phần mềm, giúp cải thiện hiệu quả tổng thể của cả ứng dụng.
Các sự kiện timer và các sự kiện I/O là hai loại hoàn toàn khác biệt789 Club, làm thế nào vòng lặp sự kiện có thể quản lý chúng một cách thống nhất? Giả sử vòng lặp sự kiện đang trong trạng thái rảnh rỗi và chờ đợi các sự kiện I/O xảy ra. Trong trường hợp này, có khả năng một sự kiện timer sẽ xuất hiện trước, nhưng vòng lặp sự kiện lại không được đánh thức kịp thời (vẫn đang chờ các sự kiện I/O). Ngược lại, nếu vòng lặp sự kiện đang chờ sự kiện timer và một sự kiện I/O xuất hiện trước, thì vẫn không thể được đánh thức đúng lúc. Do đó, chúng ta cần một cơ chế nào đó để đồng thời theo dõi cả hai loại sự kiện này. May mắn thay, một số API hệ thống có thể thực hiện điều này (như chúng ta đã đề cập trước đây về...). Cơ chế epoll )。
Giai đoạn thứ hai trong sơ đồ quy trình phía trước đã trình bày khá rõ ràng về cách thức thực hiện vòng lặp sự kiện. Ở phần nàybầu cua, chúng ta sẽ đi sâu hơn vào một số bước cụ thể và bổ sung thêm một số thông tin quan trọng cần lưu ý: Đầu tiên, khi xử lý các tác vụ, điều quan trọng là phải hiểu rõ thứ tự ưu tiên giữa các tác vụ đồng bộ và bất đồng bộ. Các tác vụ đồng bộ thường được thực thi ngay lập tức, trong khi đó, các tác vụ bất đồng bộ cần được đưa vào hàng đợi để chờ đến lượt xử lý. Thứ hai, việc quản lý bộ nhớ trong quá trình này cũng rất đáng chú ý. Cần đảm bảo rằng các đối tượng không còn được sử dụng sẽ được giải phóng đúng cách để tránh gây ra rò rỉ bộ nhớ. Cuối cùng, việc theo dõi các sự kiện ngoại lệ (exception) cũng là một khía cạnh không thể bỏ qua. Chúng ta cần thiết lập cơ chế bắt lỗi và xử lý kịp thời để hệ thống không bị gián đoạn. Hy vọng những chia sẻ trên đây sẽ giúp bạn có cái nhìn sâu sắc hơn về vòng lặp sự kiện!
acceptTcpHandler
Một quá trình đọc: Quá trình thứ
acceptUnixHandler
Thực hiện callback cho sự kiện timer
serverCron
Trong trường hợp này789 Club, nó sẽ được gọi vào bước cụ thể đó. Theo quy tắc thông thường, khi một sự kiện timer được xử lý xong, nó sẽ bị loại ra khỏi hàng đợi và không còn được thực hiện lại nữa. Tuy nhiên, có những trường hợp đặc biệt...
serverCron
Thực chấtbầu cua, điều này xảy ra vì Redis có một cơ chế nhỏ để xử lý các sự kiện timer. Khi hàm callback của timer được gọi, nó có thể trả về số mili giây cho lần thực thi 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, cho phép nó được kích hoạt lại sau đó. Ví dụ, với cài đặt mặc định, Redis... Chẳng hạn như trong trường hợp Redis chạy trên một hệ thống máy chủ, mỗi khi timer được kích hoạt, nó sẽ tự động tính toán khoảng thời gian cần thiết trước khi thực hiện lại tác vụ tương ứng. Điều này giúp đảm bảo rằng các tác vụ quan trọng sẽ luôn được thực hiện đúng thời gian mà không bị bỏ sót hay trì hoãn quá lâu. Ngoài ra, Redis còn có khả năng điều chỉnh mức độ ưu tiên cho các sự kiện timer dựa trên tải hệ thống và mức độ khẩn cấp của từng tác vụ. Điều này làm tăng hiệu suất tổng thể của hệ thống, đồng thời giảm thiểu nguy cơ xảy ra lỗi do việc quản lý thời gian không chính xác gây ra.
serverCron
Giá trị trả về là 100789 Club, do đó nó sẽ 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 điều chỉnh trong tệp cấu hình redis.conf). Thêm vào đó, Redis cung cấp nhiều cách tùy chỉnh để kiểm soát hành vi của các tác vụ định kỳ như thế này. Bạn không chỉ có thể thay đổi giá trị thời gian chờ bằng cách chỉnh sửa tham số cấu hình mà còn có thể quản lý các tác vụ thông qua API hoặc giao diện điều khiển từ xa, giúp tối ưu hóa hiệu suất theo nhu cầu cụ thể của ứng dụng.
hz
Tổng quan về quy trình xử lý yêu cầu lệnh Redis
Đến đâybầu cua, chúng ta đã hiểu rõ toàn cảnh về vòng lặp sự kiện của Redis. Quy trình xử lý chính của Redis bao gồm việc tiếp nhận yêu cầu, thực hiện lệnh và định kỳ chạy các tác vụ nền (background tasks). Ngoài ra, Redis còn được thiết kế để tối ưu hóa hiệu suất bằng cách tự động điều chỉnh các tham số dựa trên tải hiện tại, đảm bảo hệ thống luôn hoạt động ổn định trong mọi tình huống.
serverCron
Tất cả đều được thúc đẩy bởi vòng lặp sự kiện này. Khi một yêu cầu đến789 Club, sự kiện đầu vào và đầu ra (I/O) sẽ được kích hoạt, làm cho vòng lặp sự kiện thức dậy và thực hiện các lệnh dựa trên yêu cầu đó, sau đó trả về kết quả phản hồi. Đồng thời, các tác vụ đồng bộ phía sau như thu hồi các khóa hết hạn sẽ được chia thành nhiều phần nhỏ hơn, được kích hoạt bởi các sự kiện timer, xen kẽ giữa việc xử lý các sự kiện I/O để chạy định kỳ. Cách thức này cho phép chỉ sử dụng một luồng duy nhất để xử lý lượng lớn 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 phân chia đồng bộ I/O do hệ thống cung cấp. Vòng lặp sự kiện cho phép tài nguyên CPU được sử dụng theo cách luân phiên, các khối mã không thực sự chạy song song, nhưng cơ chế đa đường I/O cho phép CPU và I/O thực sự chạy cùng lúc. Hơn nữa, việc sử dụng một luồng đơn cũng mang lại lợi ích khác: nó tránh được việc thực thi đồng thời mã, vì vậy khi truy cập bất kỳ cấu trúc dữ liệu nào, không cần phải lo lắng về vấn đề an toàn luồng, từ đó giảm đáng kể độ phức tạp trong việc triển khai. Bên cạnh đó, sự tách biệt rõ ràng giữa các giai đoạn xử lý sự kiện và tác vụ nền giúp cải thiện khả năng quản lý tài nguyên. Điều này không chỉ tăng cường hiệu suất tổng thể mà còn tạo điều kiện thuận lợi cho việc bảo trì và mở rộng hệ thống trong tương lai. Với cơ chế I/O đa đường, hệ thống có thể tiếp tục thực hiện các hoạt động I/O khác nhau mà không bị gián đoạn bởi các tác vụ xử lý đồng thời, đảm bảo rằng mỗi nhiệm vụ quan trọng được hoàn thành đúng tiến độ. Chính sự kết hợp hài hòa giữa các yếu tố này đã tạo nên một hệ thống ổn định và mạnh mẽ, đủ khả năng đối phó với áp lực cao trong các tình huống thực tế.
đăng ký callback cho sự kiện I/O
acceptTcpHandler
Hai hàm callback này. Thực tế789 Club, cách mô tả này vẫn còn khá
acceptUnixHandler
Khi một client Redis gửi lệnh đến serveri9bet.com nhận 100k, quá trình này thực tế có thể được phân thành hai giai đoạn:
Thiết lập kết nối
Mã nguồn của hà
acceptTcpHandler
Hai hàm callback này. Thực tế789 Club, cách mô tả này vẫn còn khá
acceptUnixHandler
Trong hai hàm callback nàyi9bet.com nhận 100k, có nghĩa là 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 tiếp theo. Cụ thể hơn, khi một client cố gắng kết nối tới server Redis, hệ thống sẽ ghi nhận sự kiện này và chuyển nó cho vòng lặp sự kiện để xử lý. Vòng lặp sau đó sẽ tìm ra hàm callback phù hợp và chạy nó, từ đó thực hiện các tác vụ liên quan đến kết nối mới này. Đây là cách mà Redis có thể đồng thời phục vụ nhiều yêu cầu từ nhiều client khác nhau một cách hiệu quả.
acceptTcpHandler
Hai hàm callback này. Thực tếi9bet.com nhận 100k, cách mô tả này vẫn còn khá
acceptUnixHandler
Tiếp theo789 Club, từ góc độ lập trình socket, server nên gọi
Quy trình thiết lập kết nố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 tương ứng với một mô tả tệp (file descriptor) mới. Để có thể nhận các lệnh được gửi từ phía client trên kết nối mớii9bet.com nhận 100k, điều cần làm tiếp theo là đăng ký một callback xử lý 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:  Lưu ý: Ở đây, "đường-dẫn-sơ-đồ" chỉ là một vị trí giả định mà bạn có thể thay thế bằng đường dẫn thực tế của hình ảnh nếu muốn minh họa rõ hơn.
Quy trình nhận và thực hiện lệnh
readQueryFromClient
gửi lệnhi9bet.com nhận 100k, thực thi và phản hồi
readQueryFromClient
thực thi và phản hồi
Đọc dữ liệu bằng cách gọi
read
API hệ thống [8]. Mặc dù việc gọi
read
nhận diện gói dữ liệu chồng chéo
populateCommandTable
c, biến toàn cục
redisCommandTable
Trong bảng lệnh này lưu trữ các cổng vào thực thi của các lệnh Redis.
Trong phần đầu tiên của bài viết nàybầu cua, chúng ta đã đề cập rằng cần có một cơ chế để cùng lúc chờ đợi sự xuất hiện của hai loại sự kiện: I/O và timer. Cơ chế đó chính là hệ thống đa luồng I/O ở tầng cơ sở (I/O multiplexing). Tuy nhiên, trên các hệ thống khác nhau, có nhiều cơ chế đa luồng I/O khác nhau. Do đó, nhằm tạo điều kiện thuận lợi cho việc triển khai chương trình ở tầng trên, Redis đã phát triển một thư viện đơn giản dựa trên nguyên tắc lập trình hướng sự kiện, cụ thể là mã nguồn trong tệp ae.c. Thư viện này che giấu những khác biệt về cách xử lý sự kiện giữa các hệ thống khác nhau và thực hiện vòng lặp sự kiện mà chúng ta đã thảo luận từ trước đến nay. Thư viện này không chỉ giúp các nhà phát triển tập trung vào việc xây dựng ứng dụng mà còn tối ưu hóa hiệu suất bằng cách cung cấp một giao diện đồng nhất để tương tác với các sự kiện khác nhau, chẳng hạn như đọc/ghi dữ liệu, thời gian chờ, hoặc các hoạt động khác liên quan đến mạng. Điều này làm cho việc triển khai các ứng dụng phức tạp trở nên dễ dàng hơn bao giờ hết, đặc biệt khi nói đến việc quản lý tài nguyên và tối ưu hóa hiệu năng trong môi trường đa luồng.
Trong việc triển khai thư viện sự kiện của Redis789 Club, 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
Điều nà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ất789 Club, lần đầu tiên được áp dụng vào năm 1983 trong phiên bản 4.2BSD của hệ điều hành Unix. [
10
]. Đây là
POSIX
Phần của tiêu chuẩn. Ngoài rai9bet.com nhận 100k, còn có một tiêu chuẩn khác giống
select
. Miễn là hệ điều hành tuân thủ POSIX789 Club, nó có thể hỗ trợ
poll
Hệ thống gọi sử dụng [11]bầu cua, đây là tính năng lần đầu tiên xuất hiện vào năm 1986 trên hệ thống Unix SVR3 [10], và cũng tuân theo các nguyên tắc cơ bản như sau:
POSIX
Cơ chế nàyi9bet.com nhận 100k, do đó 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
Một quá trình đọc: Quá trình thứ
poll
[1]. Epoll tốt hơn
select
Một cơ chế đa hóa đầu vào/đầu ra (I/O multiplexing) được cập nhật đã xuất hiện lần đầu tiên trong nhân Linux phiên bản 2.5.44 [12]. Mục đích của việc phát triển công cụ này là để thay thế các phương pháp cũi9bet.com nhận 100k, vốn đã trở nên lỗi thời và không còn đáp ứng được yêu cầu hiệu suất cao trong các hệ thống hiện đại. Với sự cải tiến này, lập trình viên có thể quản lý nhiều luồng kết nối đồng thời một cách hiệu quả hơn, từ đó tối ưu hóa tài nguyên hệ thống và giảm thiểu độ trễ trong xử lý dữ liệu.
select
Một quá trình đọc: Quá trình thứ
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 trưng của hệ thống Linux và không thuộc tiêu chuẩn POSIX. Epoll được thiết kế để tối ưu hóa việc theo dõi các sự kiện I/O789 Club, cho phép ứng dụng xử lý hàng loạt kết nối mà không cần phải kiểm tra liên tục từng kết nối một. Điều này giúp cải thiện đáng kể hiệu suất trong các hệ thống có khối lượng công việc lớn và yêu cầu tốc độ cao.
kqueue
. Đây là một cơ chế sự kiện I/O đặc biệt 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.1789 Club, 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 trên hệ thống Linux.Bởi vì các hệ thống khác nhau có các cơ chế sự kiện riêng biệti9bet.com nhận 100k, 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 vượt trội hơn so với cơ chế đầu tiên. Tuy nhiên, việc lựa chọn cụ thể phụ thuộc vào môi trường mà Redis đang chạy và khả năng tương thích của từng cơ chế với nền tảng đó. Redis luôn cố gắng tối ưu hóa hiệu suất, do đó, nó sẽ chọn cơ chế phù hợp nhất để đảm bảo tính ổn định và tốc độ cao trong giao tiếp giữa các node hoặc giữa client và server. Điều này giúp Redis trở thành một công cụ đáng tin cậy cho nhiều ứng dụng yêu cầu xử lý dữ liệu nhanh chóng và linh hoạt trên nhiều hệ điều hành khác nhau.
select
Một quá trình đọc: Quá trình thứ
poll
; nếu biên dịch trên Linux sẽ chọn
Dựa trên bản tóm tắt ở phần trên về các cơ chế I/O được áp dụng cho các hệ điều hành khác nhaui9bet.com nhận 100k, 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, thì ở tầng sẽ chọn sử dụng
kqueue
bầu cua, đâ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ý lượng lớn kết nối cùng một lúc. Nhiều bạn làm trong lĩnh vực công nghệ thông tin chắc hẳn đã từng nghe qua về vấn đề này. Đặc biệt789 Club, trong thời đại ngày nay, khi nhu cầu về các ứng dụng trực tuyến và nền tảng giao tiếp số tăng cao, hiểu rõ cơ chế I/O trở nên vô cùng quan trọng. Điều này giúp tối ưu hóa hiệu suất của hệ thống, giảm thiểu thời gian phản hồi và đảm bảo rằng mọi yêu cầu từ người dùng đều được xử lý kịp thời, không bị gián đoạn hay trễ Chính vì thế, việc nghiên cứu sâu hơn về cách thức hoạt động của cơ chế I/O sẽ mang lại lợi ích lớn cho các nhà phát triển phần mềm hiện nay. Mã nguồn. Khi công nghệ phần cứng và mạng lưới không ngừng phát triểni9bet.com nhận 100k, việc một máy chủ độc lập có thể duy trì đến 10.000 kết nối, thậm chí lên tới hàng triệu kết nối, đã trở nên khả thi. 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. Ở đây, tôi xin giới thiệu một số bài viết blog mà bạn có thể tham khảo thêm nếu cảm thấy hứng thú (liên kết đầy đủ được cung cấp trong phần tài liệu tham khảo ở cuối bài).
Bây giờ chúng ta hãy quay lại và tìm hiểu kỹ 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 thêm từ 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
Các cơ chế sự kiện ở tầng dưới đều cung cấp một hoạt động chờ sự kiệni9bet.com nhận 100k, ví dụ như hoạt động chờ của epoll
aeCreateTimeEvent
Các cơ chế sự kiện ở tầng dưới đều cung cấp một hoạt động chờ sự kiệni9bet.com nhận 100k, ví dụ như hoạt động chờ của epoll
epoll_wait
API này cho phép thực hiện thao tác chờbầu cua, trong đó có thể chỉ định danh sách các sự kiện mong đợi (các sự kiện được biểu thị qua mô tả tệp) và đồng thời cũng có thể thiết lập khoảng thời gian chờ tối đa (tức là thời gian dài nhất cần phải chờ). Khi vòng lặp sự kiện yêu cầu chờ sự kiện xảy ra, bạn có thể gọi thao tác chờ này với 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 giá trị thời gian chờ mà thao tác cần. Để hiểu rõ hơn về cách hoạt động, xem thêm chức năng liên quan.
aeProcessEvents
Các cơ chế sự kiện ở tầng dưới đều cung cấp một hoạt động chờ sự kiệnbầu cua, ví dụ như hoạt động chờ của epollCuối cùngbầu cua, khi nói về cơ chế sự kiện, vẫn có một số thông tin đáng chú ý: ngành công nghiệp đã có sẵn một số thư viện sự kiện mã nguồn mở khá hoàn thiện. Tiêu biểu như, có các dự án nổi bật đang được cộng đồng sử dụng rộng rãi, chẳng hạn như hệ thống quản lý sự kiện linh hoạt với khả năng tùy chỉnh cao hoặc các giải pháp chuyên sâu hỗ trợ đa nền tảng, giúp người dùng dễ dàng tích hợp vào các ứng dụng của mình mà không gặp nhiều trở ngại. libevent Nguyên nhân chính có thể tóm tắt lại như sau: libev [21]. Nói chungi9bet.com nhận 100k, những thư viện nguồn mở này đã che chắn đi các chi tiết hệ thống phức tạp dưới lớp nền và đảm bảo khả năng tương thích giữa các phiên bản hệ điều hành khác nhau, mang lại giá trị rất lớn. Vậy tại sao tác giả của Redis lại quyết định 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 đã chia sẻ một số lý do. Bạn có thể tham khảo bài viết này tại đường dẫn sau:
Không muốn phụ thuộc bên ngoài quá lớn. Ví dụ
Trong phần phân tích trước của bài viếti9bet.com nhận 100k, chúng ta đã xem xét qua các quy trình xử lý mã nguồn liên quan đến việc 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 theo dõi hơn, dưới đây là một biểu đồ cây (tree diagram) thể hiện mối quan hệ gọi giữa các hàm chính (biểu đồ này khá lớn, bạn có thể nhấp để phóng to). Một lần nữa, hãy lưu ý rằng biểu đồ gọi hàm dưới đây dựa trên nhánh 5.0 của mã nguồn Redis, và có thể sẽ thay đổi trong tương lai khi kho lưu trữ mã nguồn của Redis được cập nhật. Đặc biệt, biểu đồ này tập trung vào cách các hàm chính trong Redis như `processCommand()`, `feedClient()` và `eventLoop()` được tổ chức và kết nối với nhau trong suốt quá trình hoạt động. Điều này sẽ giúp người dùng hiểu rõ hơn về cơ chế làm việc của Redis từ góc nhìn kỹ thuật.
Mỗi nhánh đi sang phải của cây biểu thị mức sâu hơn của việc gọi hàm (thực hiện nén ngăn xếp gọi).
Hình ảnh phía trên đã được bổ sung một số chú thíchi9bet.com nhận 100k, giúp dễ dàng liên kết 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:
aeSetBeforeSleepProc
Một quá trình đọc: Quá trình thứ
aeSetAfterSleepProc
Bạn đã đăng ký hai hàm gọi lạibầu cua, điều này không được đề cập trước đó trong bài viết này. Một hàm sẽ được thực hiện mỗi khi vòng lặp sự kiện bắt đầu một vòng mới, trong khi hàm kia sẽ được kích hoạt sau khi vòng lặp sự kiện kết thúc giai đoạn chờ bị chặn (tức là...). Điều thú vị ở đây là cách hai hàm gọi lại này có thể tác động đến hiệu suất và cách mà ứng dụng của bạn xử lý các sự kiện khác nhau. Điều này cho phép bạn linh hoạt tùy chỉnh hành vi của ứng dụng tại các thời điểm quan trọng trong vòng lặp sự kiện. Ví dụ như, hàm trước có thể chuẩn bị dữ liệu cần thiết cho vòng lặp, còn hàm sau có thể thực hiện các tác vụ dọn dẹp hoặc ghi nhận kết quả sau khi chờ đợi xong.
aeApiPoll
bầu cua, chính là được đăng ký vào vòng lặp sự kiện ở đây.
beforeSleep
Được đề cập trước đó
aeSetBeforeSleepProc
Thực hiện định kỳbầu cua, có nghĩa là trong
serverCron
Hàm này.
processTimeEvents
Trong quy trình xử lý dữ liệu nhận789 Club,
timeProc
Tìm bảng lệnh Redis789 Club, bảng lệnh này cũng là bảng lệnh được khởi tạo bởi
readQueryFromClient
Kết cấu toàn cục. Sau khi tìm được cổng vào lệnhbầu cua, gọi hàm
lookupCommand
server.c để thực hiện lệnh. Trong hình789 Club,
populateCommandTable
Hàm ở tầng dướibầu cua, gọi cổng vào của từng lệnh (trong hình chỉ liệt kê một số ví dụ). Ví dụ
redisCommandTable
Hàm cổng vào của lệnh
call
bầu cua, kết quả thực thi cuối cùng sẽ gọi
call
Lưu vào buffer đầu rabầu cua, tức là
get
Quy trình
getCommand
Kết cấu.
addReply
Cuối cùng789 Club, quá trình gửi kết quả thực thi lệnh đến client được thực hiện bởi
client
bầu cua, tại thời điểm thích hợp gọi lại
buf
Hai hàm callback này. Thực tếi9bet.com nhận 100k, cách mô tả này vẫn còn khá
reply
Quy trình xử lý yêu cầu Redis
beforeSleep
Một quá trình đọc: Quá trình thứ
sendReplyToClient
để thử gửi. Nếu vẫn còn dữ liệu chưa gửi hếti9bet.com nhận 100k, thì sau đó sẽ được kích hoạt lại quy trình này 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 client hay không. Nếu có789 Club, nó sẽ gọi đến phương thức xử lý tương ứng. Đây là bước quan trọng để đảm bảo rằng thông tin từ hệ thống backend được truyền tải chính xác và kịp thời đến phía client.
writeToClient
Bạn có thể thử gửi dữ liệu. Nếu việc gửi chưa hoàn tất ngay lập tứcbầu cua, bạn sẽ cần đăng ký lại một callback cho sự kiện ghi I/O vào vòng lặp sự kiện để tiếp tục xử lý. Điều này giúp đảm bảo rằng dữ liệu sẽ được truyền đi thành công mà không bị gián đoạn.
sendReplyToClient
Tóm tắt đơn giản789 Club, bài viết này hệ thống ghi lại một số quy trình thực thi như sau:
writeToClient
Quy trình khởi tạo sau khi bắt đầu từ hàm main;
beforeSleep
Logic và nguyên lý thực thi vòng lặp sự kiện;
Quá trình hoàn chỉnh từ việc nhận yêu cầu lệnh Redisbầu cua, phân tích và thực thi lệnh, đến việc phản hồi kết quả thực thi.
Để hiểu rõ mã nguồn Redis một cách suôn sẻbầu cua, bạn cần có kinh nghiệm lập trình C dưới 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, điều này có thể là một rào cản. Do đó, bài viết này sẽ ghi chép lại quá trình đọc mã nguồn của tác giả, đồng thời hệ thống hóa các vấn đề khó khăn và thách thức nổi bật mà tác giả đã gặp phải trong quá trình này. Bên cạnh đó, bài viết cũng đề xuất một số tài liệu tham khảo để hỗ trợ những ai đang muốn tìm hiểu mã nguồn Redis nhưng chưa biết bắt đầu từ đâu. Hy vọng rằng những thông tin này sẽ mang lại ít nhiều ích lợi cho các kỹ sư công nghệ đang mong muốn khám phá sâu hơn về Redis.
Redis
redisCommandTable
Nó được định nghĩa ở phần đầu của tệp nguồn server.c. Đây là nơi lưu giữ điểm vào cho việc thực thi từng lệnh Redis. Từ đâyi9bet.com nhận 100k, bạn có thể bắt đầu khám phá sâu hơn 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 mỗi lệnh không chỉ đơn thuần là một hành động, mà còn là một mảnh ghép quan trọng trong hệ thống phức tạp này, nơi mà mọi thứ đều được thiết kế để tối ưu hóa hiệu suất và sự ổn định.
Phân tích cấu trúc dữ liệu bên trong Redis
(Kết thúc)
Các bài viết được chọn lọc khác :