Computer >> Máy Tính >  >> Lập trình >> Ruby

Kafka và Ruby, một Sidekiq Lovestory

Là một APM tất cả trong một ngày càng phát triển, chúng tôi dành nhiều thời gian để đảm bảo AppSignal có thể đối phó với sự gia tăng lưu lượng truy cập của chúng tôi. Thông thường, chúng tôi không nói về cách chúng tôi làm điều đó; blog của chúng tôi có đầy đủ các bài viết về những điều tuyệt vời dưới vỏ bọc của Ruby hoặc làm những điều điên rồ với Elixir, nhưng không phải về điều gì khiến AppSignal được đánh dấu.

Tuy nhiên, lần này, chúng tôi muốn chia sẻ một số thay đổi lớn hơn trong hệ thống mà chúng tôi đã thực hiện trong vài năm qua, vì vậy chúng tôi có thể (dễ dàng) xử lý hàng tỷ yêu cầu hai chữ số được gửi theo cách của chúng tôi mỗi tháng. Trong thời gian thực. Vì vậy, hôm nay chúng tôi sử dụng kinh nghiệm mở rộng quy mô của mình để thảo luận về ngăn xếp của riêng chúng tôi và giúp bạn theo cách đó.

Từ thiết lập đường ray tiêu chuẩn đến các bộ phận tùy chỉnh khác

AppSignal bắt đầu như một thiết lập Rails khá chuẩn. Chúng tôi đã sử dụng một ứng dụng Rails thu thập dữ liệu thông qua một điểm cuối API tạo ra các công việc Sidekiq để xử lý trong nền.

Sau một thời gian, chúng tôi đã thay thế API Rails bằng phần mềm trung gian Rack để tăng tốc độ một chút và sau đó điều này được thay thế bằng máy chủ web Go giúp đẩy các công việc tương thích với Sidekiq sang Redis.

Trạng thái ứng dụng và các phần mở rộng / cập nhật

Mặc dù thiết lập này hoạt động tốt trong một thời gian dài, nhưng chúng tôi bắt đầu gặp phải các vấn đề trong đó cơ sở dữ liệu không thể theo kịp với số lượng truy vấn chạy chống lại chúng. Tại thời điểm này, chúng tôi đã xử lý hàng chục tỷ yêu cầu. Lý do chính cho điều này là mỗi quy trình Sidekiq cần lấy toàn bộ trạng thái của ứng dụng từ cơ sở dữ liệu để tăng số đếm chính xác và cập nhật tài liệu phù hợp.

Chúng tôi có thể giảm bớt điều này phần nào bằng cách sử dụng bộ nhớ đệm cục bộ của dữ liệu, nhưng do tính chất xoay vòng trong quá trình thiết lập của chúng tôi, nó vẫn có nghĩa là mỗi máy chủ cần có bộ nhớ cache đầy đủ của tất cả dữ liệu, vì chúng tôi không thể chắc chắn tải trọng của máy chủ nào sẽ kết thúc. Chúng tôi nhận ra rằng với tốc độ tăng trưởng dữ liệu mà chúng tôi đang gặp phải, việc thiết lập này sẽ trở nên bất khả thi trong tương lai.

Nhập Kafka

Để tìm kiếm một cách tốt hơn để xử lý dữ liệu, chúng tôi đã giải quyết bằng cách sử dụng Kafka làm đường dẫn xử lý dữ liệu. Thay vì tổng hợp các số liệu trong cơ sở dữ liệu, giờ đây chúng tôi tổng hợp các số liệu trong Kafka bộ xử lý . Mục tiêu của chúng tôi là đường ống Kafka của chúng tôi không bao giờ truy vấn cơ sở dữ liệu cho đến khi dữ liệu tổng hợp phải được xóa. Điều này làm giảm số lượng truy vấn trên mỗi trọng tải từ tối đa mười lần đọc và ghi xuống chỉ một lần ghi ở cuối đường dẫn.

Chúng tôi chỉ định một khóa cho mỗi tin nhắn Kafka và Kafka đảm bảo rằng các khóa giống nhau kết thúc trên cùng một phân vùng, được sử dụng bởi cùng một máy chủ. Chúng tôi sử dụng ID của ứng dụng làm khóa cho tin nhắn, điều này có nghĩa là thay vì có bộ nhớ cache cho tất cả khách hàng trên máy chủ, chúng tôi chỉ phải lưu trữ dữ liệu vào bộ nhớ cache cho các ứng dụng mà máy chủ nhận được từ Kafka, không phải tất cả các ứng dụng.

Kafka là một hệ thống tuyệt vời và chúng tôi đã di chuyển trong hai năm qua. Hiện tại, hầu hết mọi quá trình xử lý đều được thực hiện trong Rust thông qua Kafka, nhưng vẫn có những thứ được thực hiện dễ dàng hơn trong Ruby, chẳng hạn như gửi Thông báo và các tác vụ nặng về cơ sở dữ liệu khác. Điều này có nghĩa là chúng tôi cần một số cách để lấy dữ liệu từ Kafka vào ngăn xếp Rails của chúng tôi.

Kết nối Kafka và Ruby / Rails

Khi chúng tôi bắt đầu quá trình chuyển đổi này, có một vài viên ngọc Kafka Ruby, nhưng không viên nào hoạt động với bản phát hành mới nhất (tại thời điểm 0.10.x) của Kafka và hầu hết đều không bị mờ.

Chúng tôi đã xem xét việc viết đá quý của riêng mình (mà cuối cùng chúng tôi đã làm). Chúng tôi sẽ viết thêm về điều đó trong một bài báo khác. Nhưng có một người lái xe tốt chỉ là một phần của các yêu cầu. Chúng tôi cũng cần một hệ thống để sử dụng dữ liệu và thực thi các tác vụ trong Ruby và tạo ra các nhân viên mới khi các nhân viên cũ gặp sự cố.

Cuối cùng, chúng tôi đã đưa ra một giải pháp khác. Ngăn xếp Kafka của chúng tôi được xây dựng trong Rust và chúng tôi đã viết một tệp nhị phân nhỏ sử dụng sidekiq_out chủ đề và tạo các công việc tương thích với Sidekiq trong Redis. Bằng cách này, chúng tôi có thể triển khai tệp nhị phân này trên các máy công nhân của mình và nó sẽ cung cấp các công việc mới vào Sidekiq giống như cách bạn làm trong chính Rails.

Hệ nhị phân có một số tùy chọn như giới hạn số lượng dữ liệu trong Redis để ngừng sử dụng chủ đề Kafka cho đến khi ngưỡng được xóa. Bằng cách này, tất cả dữ liệu từ Kafka sẽ không nằm trong bộ nhớ của Redis đối với các nhân viên nếu có tồn đọng.

Theo quan điểm của Ruby, không có sự khác biệt nào giữa các công việc được tạo ra trong Rails và những công việc đến từ Kafka. Nó cho phép chúng tôi tạo nguyên mẫu các nhân viên mới lấy dữ liệu từ Kafka và xử lý nó trong Rails – để gửi thông báo và cập nhật cơ sở dữ liệu – mà không cần biết bất cứ điều gì về Kafka.

Nó làm cho việc di chuyển sang Kafka dễ dàng hơn vì chúng tôi có thể chuyển sang Kafka và quay lại mà không cần phải triển khai mã Ruby mới. Nó cũng giúp việc thử nghiệm trở nên siêu dễ dàng vì bạn có thể dễ dàng tạo các công việc trong bộ thử nghiệm để Ruby sử dụng mà không cần phải thiết lập toàn bộ ngăn xếp Kafka cục bộ.

Chúng tôi sử dụng Protobuf để xác định tất cả các thông báo (nội bộ) của chúng tôi, bằng cách này chúng tôi có thể khá chắc chắn rằng nếu quá trình kiểm tra vượt qua, nhân viên sẽ xử lý chính xác các công việc từ Kafka.

Cuối cùng, giải pháp này đã tiết kiệm cho chúng tôi rất nhiều thời gian và năng lượng và làm cho cuộc sống trở nên đơn giản hơn rất nhiều cho nhóm Ruby của chúng tôi.

Ưu và nhược điểm

Như với mọi thứ, có một số ưu và nhược điểm cho thiết lập này:

Ưu điểm:

  • Không yêu cầu thay đổi trong Ruby, tương thích với API
  • Dễ dàng triển khai và hoàn nguyên
  • Dễ dàng chuyển đổi giữa Kafka và Ruby
  • Redis không bị quá tải bởi các tin nhắn khi sử dụng bộ giới hạn, tiết kiệm bộ nhớ trên máy chủ, thay vào đó, giữ các tin nhắn trong Kafka.
  • Chia tỷ lệ theo chiều ngang dẫn đến bộ nhớ đệm nhỏ hơn trên mỗi máy chủ do các thông báo được khóa.

Nhược điểm:

  • Vẫn có vấn đề là mỗi luồng Sidekiq cần quyền truy cập vào bộ nhớ cache của tất cả dữ liệu cho các ứng dụng từ các phân vùng mà máy chủ sử dụng. (ví dụ:Memcache).
  • Tiến trình riêng biệt đang chạy trên máy chủ
  • Bộ xử lý gỉ cam kết bù đắp thông báo khi thông báo được gửi đến Redis, điều này có nghĩa là nó được đảm bảo nằm trong Redis, nhưng không có gì đảm bảo rằng thông báo được xử lý bởi Ruby, điều này có nghĩa là trong trường hợp máy chủ bị lỗi, có rất có thể một số thư có trong Redis nhưng không được xử lý sẽ không được xử lý.

Sidekiq và Kafka

Việc sử dụng Sidekiq đã giúp chúng tôi rất nhiều trong khi chuyển đường ống xử lý sang Kafka. Bây giờ chúng tôi đã gần như hoàn toàn rời khỏi Sidekiq và đang xử lý mọi thứ trực tiếp thông qua trình điều khiển Kafka của chúng tôi, nhưng đó là cho một bài viết khác.

Kết thúc có hậu này kết thúc câu chuyện tình yêu. Chúng tôi hy vọng bạn thích quan điểm này về hiệu suất và quy mô cũng như kinh nghiệm mở rộng AppSignal của chúng tôi. Chúng tôi hy vọng câu chuyện này về các quyết định mà chúng tôi đã đưa ra xung quanh ngăn xếp của mình sẽ giúp ích cho bạn.

Kiểm tra phần còn lại của blog hoặc theo dõi chúng tôi để theo dõi khi tập tiếp theo về thiết lập Kafka của chúng tôi được xuất bản. Và nếu cuối cùng bạn đang tìm kiếm một APM tất cả trong một thực sự là của các nhà phát triển dành cho các nhà phát triển, hãy đến tìm chúng tôi.