Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Nâng cao Ruby App với Kafka: Câu chuyện Thành công Sidekiq Đã Chứng minh

Là một APM all-in-one đang phát triển không ngừng, chúng ta dành nhiều thời gian để đảm bảo AppSignal có thể đối phó với sự tăng trưởng traffic. Thông thường, chúng ta không nói về cách chúng ta làm điều đó; blog của chúng ta đầy ắp các bài viết về những điều tuyệt vời bên trong Ruby hoặc làm những điều điên rồ với Elixir, nhưng không nói về cái gì khiến AppSignal "tick".

Tuy nhiên, lần này chúng ta muốn chia sẻ một số thay đổi lớn trong stack của mình đã thực hiện qua vài năm qua, để có thể (dễ dàng) xử lý hàng chục tỷ request được gửi đến mỗi tháng. Real-time. Vì vậy hôm nay chúng ta dùng kinh nghiệm scaling của mình để thảo luận về stack của chính chúng ta và giúp các bạn theo cách đó.

Từ Standard Rails Setup đến Các Phần Tùy chỉnh Hơn

AppSignal bắt đầu như một thiết lập Rails khá chuẩn. Chúng ta dùng một Rails app thu thập dữ liệu qua một API endpoint tạo ra các Sidekiq job để xử lý trong background.

Sau một thời gian, chúng ta thay thế Rails API bằng một Rack middleware để đạt được một chút tốc độ và sau đó cái này được thay thế bằng một Go web server đẩy các Sidekiq-compatible job vào Redis.

Trạng thái App và Increments/Updates (Tăng/Giảm)

Mặc dù thiết lập này hoạt động tốt trong một thời gian dài, chúng ta bắt đầu gặp vấn đề nơi database không thể theo kịp lượng query chạy trên chúng. Lúc này chúng ta đã xử lý hàng chục tỷ request. Lý do chính là mỗi Sidekiq process cần lấy toàn bộ trạng thái app từ database để increment đúng counter và update đúng document.

Chúng ta có thể giảm bớt điều này một chút với việc cache dữ liệu cục bộ, nhưng do bản chất round-robin của thiết lập, nó vẫn có nghĩa là mỗi server cần có cache đầy đủ của tất cả dữ liệu, vì chúng ta không thể chắc chắn payload sẽ rơi vào server nào. Chúng ta nhận ra rằng với sự tăng trưởng dữ liệu chúng ta đang trải qua, thiết lập này sẽ trở nên không thể trong tương lai.

Kafka Xuất Hiện

Trong tìm kiếm một cách tốt hơn để xử lý dữ liệu, chúng ta quyết định dùng Kafka làm pipeline xử lý dữ liệu. Thay vì aggregate metrics trong database, bây giờ chúng ta aggregate metrics trong các Kafka processors. Mục tiêu của chúng ta là pipeline Kafka không bao giờ query database cho đến khi dữ liệu aggregate phải được flush. Điều này làm giảm số lượng query per payload từ lên tới mười reads và writes xuống còn chỉ một write ở cuối pipeline.

Chúng ta chỉ định một key cho mỗi Kafka message và Kafka đảm bảo các key giống nhau kết thúc trên cùng một partition, được consume bởi cùng một server. Chúng ta dùng app ID làm key cho messages, điều này có nghĩa là thay vì có cache cho tất cả customer trên server, chúng ta chỉ cần cache dữ liệu cho các app mà server nhận từ Kafka, không phải tất cả apps.

Kafka là một hệ thống tuyệt vời và chúng ta đã migrate trong hai năm qua. Ngay bây giờ hầu như tất cả xử lý được thực hiện trong Rust qua Kafka, nhưng vẫn còn những việc dễ làm hơn trong Ruby, như gửi Notifications và các task heavy-database khác. Điều này có nghĩa là chúng ta cần một cách để lấy dữ liệu từ Kafka đến Rails stack của mình.

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

Khi chúng ta bắt đầu chuyển đổi này, có một vài Kafka Ruby gem, nhưng không cái nào làm việc với bản release Kafka mới nhất (lúc đó là 0.10.x) và hầu hết không được maintain.

Chúng ta xem xét việc viết gem riêng (chúng ta cuối cùng cũng làm). Chúng ta sẽ viết thêm về điều đó trong một bài khác. Nhưng có một driver đẹp chỉ là một phần yêu cầu. Chúng ta cũng cần một hệ thống để consume dữ liệu và thực thi task trong Ruby và spawn worker mới khi worker cũ crash.

Cuối cùng chúng ta tìm ra một giải pháp khác. Stack Kafka của chúng ta được xây dựng bằng Rust và chúng ta viết một binary nhỏ consume topic sidekiq_out và tạo Sidekiq-compatible job trong Redis. Cách này chúng ta có thể deploy binary này trên worker machine và nó sẽ feed job mới vào Sidekiq giống như bạn làm trong chính Rails.

Binary này có một vài option như giới hạn lượng dữ liệu trong Redis để dừng consume Kafka topic cho đến khi threshold được clear. Cách này tất cả dữ liệu từ Kafka sẽ không kết thúc trong bộ nhớ Redis trên worker nếu có backlog.

Từ góc độ Ruby, không có sự khác biệt nào giữa job được sinh trong Rails và job đến từ Kafka. Nó cho phép chúng ta prototype worker mới lấy dữ liệu từ Kafka và xử lý trong Rails — để gửi notification và update database — mà không cần biết bất cứ điều gì về Kafka.

Nó làm cho việc migrate sang Kafka dễ dàng hơn vì chúng ta có thể switch qua lại giữa Kafka và Ruby mà không cần deploy Ruby code mới. Nó cũng làm testing siêu dễ dàng vì bạn có thể dễ dàng generate job trong test suite để được consume bởi Ruby mà không cần setup toàn bộ Kafka stack locally.

Chúng ta dùng Protobuf để định nghĩa tất cả message (internal) của mình, cách này chúng ta có thể khá chắc chắn rằng nếu test pass, worker sẽ xử lý đúng job từ Kafka.

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

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

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

Ưu điểm:

  • Không cần thay đổi Ruby, API compatible
  • Dễ deploy và revert
  • Dễ switch giữa Kafka và Ruby
  • Redis không bị overload bởi messages khi dùng limiter, tiết kiệm bộ nhớ server, giữ messages trong Kafka.
  • Horizontal scaling dẫn đến cache nhỏ hơn trên mỗi server, nhờ keyed messages.

Nhược điểm:

  • Vẫn còn vấn đề mỗi Sidekiq thread cần truy cập cache của tất cả dữ liệu cho apps từ partitions server consume. (ví dụ Memcache).
  • Process riêng chạy trên server
  • Rust processor commit message offset khi message được flush vào Redis, điều này đảm bảo nó ở trong Redis, nhưng không đảm bảo message được xử lý bởi Ruby, nghĩa là trong trường hợp server crash, có cơ hội một số message ở trong Redis nhưng chưa được xử lý sẽ không được xử lý.

Sidekiq và Kafka

Việc dùng Sidekiq giúp chúng ta 엄청나게 trong khi migrate processing pipeline sang Kafka. Chúng ta hiện đã gần như hoàn toàn chuyển khỏi Sidekiq và xử lý mọi thứ qua Kafka driver trực tiếp, nhưng đó là chuyện cho bài khác.

Cái happy ending này kết thúc câu chuyện tình yêu. Hy vọng bạn thích góc nhìn này về performance và scaling, và kinh nghiệm scaling AppSignal của chúng ta. Hy vọng câu chuyện về các quyết định chúng ta làm quanh stack của mình, lần lượt, giúp ích cho bạn.

Xem thêm phần còn lại của blog hoặc follow chúng ta để theo dõi khi episode tiếp theo về setup Kafka của chúng ta được publish. Và nếu bạn cuối cùng tìm kiếm một APM all-in-one thực sự by developers for developers, hãy tìm chúng tôi.