Trang chủ > Công nghệ máy chủ > Nội dung chính

Bạn nên bắt đầu đọc mã nguồn Redis từ đâu?


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:

  1. Trước tiênbầu cua, tóm tắt toàn bộ quy trình khởi tạo mã nguồn (bắt đầu từ hàm main) và cấu trúc vòng lặp sự kiện;
  2. Sau đói9bet.com nhận 100k, tóm tắt quy trình xử lý yêu cầu lệnh Redis;
  3. Tập trung giới thiệu cơ chế sự kiện;
  4. Đối với từng quy trình xử lý mã nguồn được giới thiệu trước đó789 Club, cung cấp mối quan hệ gọi chi tiết để tiện tra cứu bất cứ lúc nào;

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.

Tóm tắt quy trình khởi tạo và vòng lặp sự kiện

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.

  • Khởi tạo nhiều loại khác nhau (bao gồm cả khởi tạo vòng lặp sự kiện);
  • Chạy vòng lặp sự kiện.

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):

Sơ đồ quy trình khởi tạo và vòng lặp sự kiệ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:

  • Tải cấu hình và khởi tạo Bước này cho thấy việc khởi tạo các cấu trúc dữ liệu cơ bản và các tham số khác nhau của máy chủ Redis. Trong mã nguồn của Redisi9bet.com nhận 100k, máy chủ Redis được biểu diễn bằng một cấu trúc được gọi là `redisServer`, nơi chứa tất cả các tham số cần thiết để máy chủ hoạt động. Điều này bao gồm cổng mà nó đang lắng nghe, mô tả tệp (file descriptor), danh sách các client hiện đang kết nối, bảng lệnh Redis (command table), các tham số liên quan đến quá trình lưu trữ dữ liệu, v.v., cũng như cấu trúc vòng lặp sự kiện mà chúng ta sẽ thảo luận ngay sau đây. Khi chạy, máy chủ Redis thực chất được duy trì bởi một đối tượng duy nhất này, đóng vai trò như bộ não điều phối toàn bộ hoạt động của hệ thống. 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.
  • Tạo vòng lặp sự kiện Trong Redisbầu cua, vòng lặp sự kiện được biểu thị bằng một cấu trúc gọi là 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).
  • Bắt đầu lắng nghe socket Chương trình máy chủ cần phải lắng nghe để nhận các yêu cầu. Tùy thuộc vào cấu hìnhi9bet.com nhận 100k, bước này có thể thiết lập hai loại lắng nghe khác nhau: một là lắng nghe cho các kết nối TCP và hai là lắ .. (Trong ngữ cảnh này, tôi sẽ tiếp tục bằng cách thêm một số chi tiết kỹ thuật liên quan mà vẫn phù hợp với ý nghĩa ban đầu.) ...các yêu cầu từ giao thức UDP. Mỗi loại lắng nghe đều phục vụ những mục đích riêng biệt, trong đó TCP thường được sử dụng khi cần đảm bảo tính toàn vẹn của dữ liệu, còn UDP lại ưu tiên tốc độ và khả năng xử lý dữ liệu không đồng bộ.) Unix domain socket Unix domain socket IPC [3]). Nó cũng có POSIX Định nghĩa rõ ràng trong tiêu chuẩn [4]. Đăng ký callback cho sự kiện timer socket miền Unix 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.
  • Đăng ký callback cho sự kiện I/O Redisbầu cua, với tư cách là một chương trình hoạt động theo mô hình đơn luồng (single-threaded), nếu muốn lên lịch thực hiện các tác vụ bất đồng bộ, chẳng hạn như thu hồi các khóa (keys) đã hết hạn định kỳ, sẽ không có cách nào khác ngoài việc phụ thuộc vào cơ chế vòng lặp sự kiện (event loop). Quy trình này bao gồm việc đăng ký một sự kiện timer vào vòng lặp sự kiện mà bạn vừa tạo ra trước đó và cấu hình nó để có thể thực thi định kỳ một hàm gọi lại (callback) cụ thể. 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.
  • [6] để hai chiều giao tiếp với module. Điều này không phải là trọng tâm của bài viếtbầu cua, vì vậy chúng tôi tạm thời bỏ qua nó.Một trong những công việc chính của Redis server là lắng nghe các sự kiện I/Oi9bet.com nhận 100k, từ đó phân tích yêu cầu lệnh từ phía client, thực thi lệnh và trả về kết quả phản hồi. Việc lắng nghe các sự kiện I/O tất nhiên cũng phụ thuộc vào vòng lặp sự kiện. Như đã đề cập trước đây, Redis có thể thiết lập hai loại lắng nghe: một là cho các kết nối TCP và hai là cho các cổng kết nố Do đó, ở đây cần đăng ký hai hàm callback tương ứng để xử lý từng loại sự kiện I/O này. Hai hàm callback đó lần lượt là: 1. Hàm xử lý sự kiện từ kết nối TCP 2. Hàm xử lý sự kiện từ cổng kết nối Unix domain socket 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
  • Hàm789 Club, về nguyên tắc, những nhiệm vụ mà luồng nền thực hiện dường như cũng có thể được đặt vàoRedis sẽ tạo ra một số luồng bổ sung để chạy ở chế độ nềnbầu cua, chuyên trách việc xử lý các tác vụ tiêu tốn thời gian nhưng có thể bị trì hoãn thực hiện (thường là các công việc dọn dẹp). Những luồng nền này trong Redis được gọi là bio (Background I/O Service). Chúng đảm nhận các nhiệm vụ như: thao tác đóng tệp có thể bị trì hoãn (như khi thực hiện lệnh unlink), hoạt động ghi vào cơ sở dữ liệu của AOF (tức là thực thi lệnh fsync, nhưng lưu ý rằng chỉ những thao tác fsync có thể bị trì hoãn mới được xử lý bởi các luồng nền), cũng như một số thao tác xóa key lớn (như khi thực hiện lệnh flushdb async). Điều thú vị là cái tên bio có vẻ không hoàn toàn phù hợp, vì những gì nó thực hiện không nhất thiết liên quan đến I/O. Với các luồng nền này, chúng ta có thể đặt ra câu hỏi: quá trình khởi tạo ban đầu đã đăng ký một callback sự kiện timer, tức là... 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.
  • Bây giời9bet.com nhận 100k, chúng ta tiếp tục thảo luận giai đoạn thứ hai trong sơ đồ quy trình trên: Vòng lặp sự kiện.Bạn đã thiết lập xong cấu trúc của vòng lặp sự kiệni9bet.com nhận 100k, nhưng phần logic thực sự để bắt đầu vòng lặp vẫn chưa được triển khai. Khi bước này hoàn tất, vòng lặp sẽ chính thức hoạt động, kích hoạt liên tục các hàm callback đã đăng ký trước đó cho các sự kiện timer và I/O, từ đó duy trì hiệu suất chạy của toàn hệ thống.

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!

  • Đợi sự kiện xảy ra Như đã đề cập trước đói9bet.com nhận 100k, vòng lặp sự kiện cần phải chờ đợi cả sự kiện timer và các sự kiện I/O. Đối với các sự kiện I/O, chỉ cần xác định rõ các mô tả tệp mà nó cần chờ là đủ; còn đối với sự kiện timer, cần phải thực hiện một số so sánh để xác định chính xác thời gian cần chờ trong vòng lặp hiện tại. Trong quá trình vận hành hệ thống, có thể có nhiều callback của sự kiện timer được đăng ký, ví dụ như yêu cầu thực thi một callback sau 100 miligiây và đồng thời yêu cầu thực thi một callback khác sau 200 miligiây. Điều này buộc vòng lặp sự kiện phải thực hiện bước đầu tiên trước mỗi lần chạy của nó, đó là tìm ra sự kiện timer nào cần thực thi sớm nhất. Khi điều này được xác định, vòng lặp sự kiện sẽ biết mình cần chờ bao lâu (trong trường hợp này, chúng ta cần chờ 100 miligiây). Bằng cách này, việc quản lý thời gian của vòng lặp sự kiện trở nên hiệu quả hơn, đảm bảo rằng các hoạt động cần thiết được thực hiện đúng thời điểm mà không làm chậm trễ hoặc bỏ sót bất kỳ sự kiện nào. Điều này đặc biệt quan trọng trong các ứng dụng đòi hỏi độ chính xác cao và khả năng xử lý đa tác vụ mượt mà.
  • bầu cua, sau khi tìm kiếm xong có thể có ba kết quả khác nhau, do đó bước chờ đợi này cũng có thể có ba trường hợp tương ứng:Ở bước này789 Club, chúng ta cần có khả năng chờ đợi đồng thời cả sự kiện từ timer và các sự kiện I/O. Để đạt được điều đó, chúng ta dựa vào cơ chế đa dùng I/O (I/O multiplexing) từ tầng hệ thống. Cơ chế này thường được thiết kế theo cách cho phép chúng ta chờ đợi các sự kiện I/O liên quan đến nhiều mô tả tệp (file descriptor), đồng thời cũng có thể đặt một khoảng thời gian chờ tối đa để hạn chế việc bị khóa quá lâu. Nếu trong khoảng thời gian chờ này, có sự kiện I/O xảy ra, chương trình sẽ được đánh thức để tiếp tục thực thi; còn nếu không có sự kiện I/O nào xảy ra và thời gian chờ đã hết, chương trình cũng sẽ được đánh thức. Đối với việc chờ đợi sự kiện timer, chúng ta sử dụng chính cơ chế này để quản lý thời gian. Tất nhiên, khoảng thời gian chờ có thể được đặt thành vô hạn, điều này có nghĩa là chương trình sẽ chỉ chờ sự kiện I/O mà thôi. Bây giờ, hãy cùng xem xét lại bước trước đó. Đợi sự kiện xảy ra Xác định xem có sự kiện I/O xảy ra hay đã quá hạn
    • Trường hợp đầu tiêni9bet.com nhận 100k, bạn tìm thấy một sự kiện timer gần nhất yêu cầu kích hoạt tại một thời điểm cụ thể trong tương lai. Ở bước này, tất cả những gì cần làm là chuyển đổi thời gian kích hoạt đó thành khoảng thời gian chờ (timeout). Điều này có nghĩa là hệ thống sẽ tạm dừng và chờ đến khi thời gian được chỉ định đạt đến, sau đó mới tiếp tục thực hiện hành động tiếp theo.
    • Trường hợp thứ haii9bet.com nhận 100k, bạn đã tìm thấy một sự kiện timer gần nhất, nhưng thời điểm mà nó yêu cầu đã trôi qua. Khi đó, sự kiện này cần được kích hoạt ngay lập tức mà không cần phải chờ đợi thêm nữa. Tuy nhiên, trong quá trình triển khai thực tế, vẫn có thể sử dụng API chờ đợi sự kiện, chỉ cần thiết lập giá trị thời gian chờ (timeout) thành 0 để đạt được mục đích này. Ngoài ra, điều này cũng cho thấy rằng việc quản lý thời gian trong hệ thống cần phải thật chính xác. Nếu không cẩn thận, các sự kiện timer có thể bị trì hoãn vô ích hoặc thậm chí bị bỏ qua hoàn toàn. Vì vậy, khi viết mã, bạn nên luôn kiểm tra kỹ lưỡng các tham số liên quan đến thời gian và đảm bảo rằng mọi thứ đều hoạt động như mong muốn.
    • Trường hợp thứ babầu cua, không tìm thấy bất kỳ sự kiện timer nào đã được đăng ký. Khi đó, thời gian chờ nên được đặt thành vô hạn để hệ thống tiếp tục chờ mà không thực hiện hành động gì. Chỉ khi có sự kiện đầu vào (I/O) xảy ra thì chương trình mới được đánh thức và tiếp tục xử lý. Trong tình huống này, hệ thống sẽ ở trạng thái tĩnh cho đến khi một tín hiệu từ thiết bị ngoại vi hoặc hoạt động liên quan đến I/O xuất hiện. Điều này giúp tối ưu hóa tài nguyên và giảm thiểu việc tiêu tốn năng lượng trong trường hợp không có sự kiện nào cần phản hồi ngay lập tức.
  • Thực hiện callback cho sự kiện I/O Khi chương trình tiếp tục thực thi sau khi thoát khỏi trạng thái bị tạm ngừng ở bước trước đó789 Club, logic đánh giá sẽ được thực hiện. Nếu một sự kiện I/O xảy ra, trước tiên hàm callback của sự kiện I/O sẽ được gọi, sau đó nếu có bất kỳ sự kiện thời gian nào đã đến hạn, hàm callback của nó cũng sẽ được thực hiện (nếu tồn tại). Ngược lại, nếu thời gian chờ trước hết hết hạn, điều này có nghĩa là chỉ có sự kiện thời gian cần được kích hoạt (không có sự kiện I/O nào xảy ra), vì vậy hàm callback của sự kiện thời gian đã đến hạn sẽ được thực hiện ngay lập tức. Bên cạnh đó, trong quá trình này, hệ thống cũng cần đảm bảo rằng tất cả các yêu cầu xử lý đều được ưu tiên đúng cách và không gây ra xung đột giữa các loại sự kiện khác nhau. Điều này giúp đảm bảo tính ổn định và hiệu quả trong việc quản lý tài nguyên và luồng dữ liệu của ứng dụng.
  • 789 Club, được gọi trong bước này.Những hàm callback được sử dụng để theo dõi kết nối TCP và theo dõi socket domain Unix mà chúng ta đã đề cập trước đó là hai loại sự kiện I/O hoàn toàn khác nhau. Mỗi loại có cách xử lý riêngi9bet.com nhận 100k, nhưng cả hai đều đóng vai trò quan trọng trong việc quản lý luồng dữ liệu trên hệ thống. Việc thiết lập các hàm callback này đòi hỏi phải hiểu rõ cơ chế hoạt động của từng loại giao tiếp, từ việc thiết lập kết nối TCP đến việc truyền nhận dữ liệ Cả hai đều yêu cầu lập trình viên phải cẩn trọng trong việc xử lý lỗi và tối ưu hóa hiệu suất để đảm bảo hệ thống hoạt động ổn định và hiệu quả. acceptTcpHandler Một quá trình đọc: Quá trình thứ acceptUnixHandler Thực hiện callback cho sự kiện timer
  • . Các hàm callback định kỳ mà chúng tôi đề cập trước đó Biến để điều chỉnh). 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ế.

hoặc

đă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

  1. Client gửi yêu cầu kết nối (qua TCP hoặc )bầu cua, server chấp nhận kết nối. Unix domain socket Gửi789 Club, thực hiện và phản hồi lệnh
  2. Thiết lập kết nối Sau khi kết nối được thiết lập789 Club, client có thể gửi dữ liệu lệnh qua kết nối mới này. Sau khi nhận được lệnh, server sẽ thực hiện nó và gửi lại kết quả thự Đặc biệt, trên chính kết nối này, chu trình “gửi lệnh – thực thi – phản hồi” hoàn toàn có thể được lặp đi lặp lại nhiều lần, tạo ra một dòng giao tiếp hai chiều mượt mà giữa client và server. Điều này giúp tăng cường hiệu quả làm việc, cho phép các tương tác phức tạp hơn được xử lý một cách nhanh chóng và linh hoạt.

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: ![Sơ đồ quy trình](đường-dẫn-sơ-đồ) 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.

Từ sơ đồ quy trình trên, 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à

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ó một số điểm đáng chú ý trong sơ đồ quy trình trên:

Đọc dữ liệu bằng cách gọi

  • Khi đọc dữ liệu từ socketbầu cua, phương thức được sử dụng là theo kiểu luồng (stream). Tức là, từ góc độ tầng ứng dụng, dữ liệu được truyền tải từ lớp mạng dưới cùng sẽ tồn tại dưới dạng một dòng byte không ngừng nghỉ. Để có thể hiểu và xử lý tiếp, chúng ta cần phân tích dòng byte này để tách ra các lệnh Redis hoàn chỉnh. Tuy nhiên, do đặc tính của việc truyền tải trên mạng, chúng ta không thể kiểm soát chính xác số lượng byte được đọc trong một lần. Thực tế, ngay cả khi máy chủ chỉ nhận được một phần dữ liệu của một lệnh Redis (chẳng hạn như chỉ một byte), điều đó vẫn có thể kích hoạt sự kiện callback I/O. Khi đó, chúng ta cần gọi đến... 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
  • nh stick package
  • Biểu hiện thứ hai của việc xử lý "dữ liệu dính" (còn được gọi là "nhóm dữ liệu") có thể được nhìn thấy trong vòng lặp lớn trong sơ đồ quy trình ở trên. Miễn là bộ đệm tạm thời lưu trữ dữ liệu đầu vào query buffer vẫn còn dữ liệu để xử lýi9bet.com nhận 100k, hệ thống sẽ liên tục cố gắng phân tích và trích xuất các lệnh hoàn chỉnh cho đến khi tất cả các lệnh trong đó đều được giải quyết xong. Khi đó, vòng lặp mới chấm dứt hoạt động. Một điểm đáng chú ý là ngay cả khi có sự gián đoạn trong quá trình này, chẳng hạn như một kết nối mạng không ổn định hoặc một lỗi phần mềm nhỏ, hệ thống vẫn sẽ tiếp tục cố gắng tái lập trạng thái và tiếp tục phân tích các lệnh còn lại từ nơi bị gián đoạn trước đó. Điều này đảm bảo rằng không có dữ liệu nào bị mất hoặc bỏ qua trong quá trình xử lý.
  • Tìm bảng lệnhi9bet.com nhận 100k, tức là tìm kiếm bảng lệnh được khởi tạo bởi 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.
  • Kết quả thực thi lệnh trong sơ đồ quy trình ở trên chỉ được lưu tạm thời vào một bộ đệm đầu ra (output buffer) và chưa thực sự được gửi đến khách hàng. Quy trình giao tiếp với khách hàng không nằm trong phạm vi của quy trình nàyi9bet.com nhận 100k, mà được xử lý bởi một tiến trình khác cũng được điều khiển bởi vòng lặp sự kiện (event loop). Tiến trình này bao gồm nhiều chi tiết phức tạp mà chúng ta sẽ tạm thời bỏ qua ở đây, và sẽ dành thời gian thảo luận chi tiết hơn về nó trong phần thứ tư sau này.

Giới thiệu về cơ chế sự kiện

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
  • Cơ chế epoll Cơ chế 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.
  • event ports Hệ thống [15]. illumos Phương án hiệu quả hơnbầu cua, vì vậy Redis ưu tiên chọn ba cơ chế sau.

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 đó).

  • Trước tiêni9bet.com nhận 100k, khi đăng ký các cho sự kiện I/O vào vòng lặp sự kiện, bạn cần chỉ định callback nào sẽ được gắn với sự kiện nào (các sự kiện này được biểu diễn bằng mô tả tệp - file descriptor). Mối quan hệ giữa sự kiện và callback được duy trì bởi thư viện điều khiển sự kiện do Redis cung cấp ở tầng trên. Để hiểu rõ hơn về cách thức hoạt động, hãy tham khảo hàm sau đây: [Thay vì giữ nguyên từ hàm, tôi đã thêm ngữ cảnh để làm cho câu trở nên tự nhiên hơn trong tiếng Việt] 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
  • Tương tựi9bet.com nhận 100k, khi đăng ký callback của sự kiện timer vào vòng lặp sự kiện, bạn cần chỉ định thời gian bao lâu sau sẽ thực hiện callback nào. Ở đây, cần lưu giữ thông tin về callback nào dự kiến được gọi tại thời điểm nào, và điều này được duy trì bởi thư viện điều khiển sự kiện do lớp trên của Redis cung cấp. Để hiểu rõ hơn, hãy tham khảo hàm... 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
  • [20] và 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 epoll
  • Khi được đánh thức từ bước chờ đợi trước đói9bet.com nhận 100k, có hai trường hợp xảy ra: nếu một sự kiện đầu vào (I/O) đã xảy ra, hãy tìm và gọi hàm callback tương ứng với sự kiện đó; còn nếu thời gian chờ đã hết, hãy kiểm tra tất cả các sự kiện timer đã đăng ký trước đó. Đối với những hàm callback mà thời điểm dự kiến thực thi vượt quá thời điểm hiện tại, hãy gọi chúng để xử lý. Có thể thấy rằng mỗi trường hợp đều yêu cầu cách tiếp cận riêng biệt. Khi một sự kiện I/O được kích hoạt, hệ thống sẽ nhanh chóng xác định sự kiện cụ thể và thực hiện hành động cần thiết ngay lập tức. Trong khi đó, khi đến thời điểm hết hạn của timer, toàn bộ danh sách các sự kiện thời gian được kiểm tra kỹ lưỡng để đảm bảo rằng không bỏ sót bất kỳ callback nào cần được thực thi. Điều này cho phép hệ thống hoạt động ổn định và hiệu quả trong mọi tình huống.

Cuố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ụ

  • Quá lớn789 Club, lớn hơn cả kho mã nguồn của Redis. libevent Dễ dàng phát triển tùy chỉnh hơn.
  • Đôi khi các thư viện bên thứ ba sẽ xuất hiện lỗi không ngờ tới.
  • Mối quan hệ gọi mã nguồn

Ý nghĩa của cấu trúc cây nàyi9bet.com nhận 100k, trước tiên hãy giới thiệu:

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

  • Sơ đồ cây này không biểu thị đầy đủ tất cả mối quan hệ gọi hàm789 Club, chỉ liệt kê các quy trình gọi liên quan đến bài viết này.
  • Tiếp tục di chuyển sang phải đến nhánh cuối cùngbầu cua, điều này có nghĩa là không còn hàm nào được gọi tiếp (điều này biểu thị rằng ngăn xếp gọi (call stack) đang được thu lại, và quyền kiểm soát sẽ được trả về cho vòng lặp sự kiện). Bên cạnh đó, khi đạt đến nhánh này, hệ thống cũng tạm thời lưu lại trạng thái hiện tại của chương trình để đảm bảo rằng mọi thứ sẽ tiếp tục hoạt động chính xác khi các hàm trước đó hoàn thành việc xử lý.
  • Trong hìnhbầu cua, có tổng cộng 6 cây đại diện cho các tiến trình độc lập, trong đó cây đầu tiên chính là điểm vào của hàm main. Năm cây còn lại đều được kích hoạt từ vòng lặp sự kiện, tạo ra các luồng thực thi mới. Cây bên trái cùng đóng vai trò là gốc bắt đầu của quá trình xử lý.
  • Sơ đồ mối quan hệ gọi mã nguồn quan trọng

Quy trình khởi tạo thêm

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:

  • Sau khi trả về (gọi lại). Điểm nhập của quy trình gọi thứ năm ở dưới cùng trong sơ đồ 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
  • Chi nhánh gọi nàyi9bet.com nhận 100k, hàm được gọi 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
  • Khi khởi tạo ban đầu 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
  • Callback. 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.

  • Dù bỏ qua nhiều chi tiết trong bài viết nàybầu cua, có lẽ bạn ít nhất có thể nhớ biến toàn cục bảng lệnh Redis:
  • Giống như loạt bài viết trước đã làm.
  • Chúc bạn đọc mã nguồn vui vẻ!

Để 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)

Tài liệu tham khảo:

Các bài viết được chọn lọc khác


Bài viết gốci9bet.com nhận 100k, xin vui lòng trích dẫn nguồn và bao gồm mã QR bên dưới! Nếu không, từ chối tái bản!
Liên kết bài viết: /dumkj3ht.html
Hãy theo dõi tài khoản Weibo cá nhân của tôi: Tìm kiếm tên tôi "Trương Thiết Lệ" trên Weibo.
Tài khoản WeChat của tôi: tielei-blog (Trương Thiết Lệ)
Bài trước: Buôn chuyện về kinh doanh và nền tảng
Bài sau: Linh hồn vạn vật: Tình yêu của các linh hồn

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