paint-brush
Khả năng quan sát Java Backend với OpenTelemetry Traces và mã tối thiểutừ tác giả@apanasevich
292 lượt đọc

Khả năng quan sát Java Backend với OpenTelemetry Traces và mã tối thiểu

từ tác giả Dmitriy Apanasevich10m2024/11/15
Read on Terminal Reader
Read this story w/o Javascript

dài quá đọc không nổi

Cách chúng tôi tích hợp khung OpenTelemetry vào phần phụ trợ Java, theo dõi dữ liệu với lượng mã hóa tối thiểu.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Khả năng quan sát Java Backend với OpenTelemetry Traces và mã tối thiểu
Dmitriy Apanasevich HackerNoon profile picture

Xin chào mọi người! Tôi là Dmitriy Apanasevich, Nhà phát triển Java tại MY.GAMES, đang làm việc trên trò chơi Rush Royale, và tôi muốn chia sẻ kinh nghiệm tích hợp khung OpenTelemetry vào phần phụ trợ Java của chúng tôi. Có khá nhiều điều cần đề cập ở đây: Chúng tôi sẽ đề cập đến những thay đổi mã cần thiết để triển khai nó, cũng như các thành phần mới mà chúng tôi cần cài đặt và cấu hình - và tất nhiên, chúng tôi sẽ chia sẻ một số kết quả của mình.

Mục tiêu của chúng tôi: đạt được khả năng quan sát hệ thống

Hãy cung cấp thêm một số bối cảnh cho trường hợp của chúng tôi. Là nhà phát triển, chúng tôi muốn tạo ra phần mềm dễ theo dõi, đánh giá và hiểu (và đây chính xác là mục đích của việc triển khai OpenTelemetry — để tối đa hóa hệ thống khả năng quan sát ).


Các phương pháp truyền thống để thu thập thông tin chi tiết về hiệu suất ứng dụng thường liên quan đến việc ghi nhật ký thủ công các sự kiện, số liệu và lỗi:



Tất nhiên, có nhiều khuôn khổ cho phép chúng ta làm việc với nhật ký và tôi chắc rằng mọi người đọc bài viết này đều có hệ thống được cấu hình để thu thập, lưu trữ và phân tích nhật ký.


Việc ghi nhật ký cũng đã được cấu hình đầy đủ cho chúng tôi, do đó chúng tôi không sử dụng các chức năng do OpenTelemetry cung cấp để làm việc với nhật ký.


Một cách phổ biến khác để giám sát hệ thống là sử dụng các số liệu:


Chúng tôi cũng có một hệ thống được cấu hình đầy đủ để thu thập và trực quan hóa số liệu, vì vậy ở đây chúng tôi cũng bỏ qua khả năng của OpenTelemetry về mặt làm việc với số liệu.


Nhưng một công cụ ít phổ biến hơn để thu thập và phân tích loại dữ liệu hệ thống này là dấu vết .


Một dấu vết biểu thị đường dẫn mà một yêu cầu đi qua hệ thống của chúng tôi trong suốt vòng đời của nó và nó thường bắt đầu khi hệ thống nhận được yêu cầu và kết thúc bằng phản hồi. Dấu vết bao gồm nhiều nhịp , mỗi phần đại diện cho một đơn vị công việc cụ thể do nhà phát triển hoặc thư viện họ lựa chọn xác định. Các khoảng này tạo thành một cấu trúc phân cấp giúp hình dung cách hệ thống xử lý yêu cầu.


Trong cuộc thảo luận này, chúng ta sẽ tập trung vào khía cạnh theo dõi của OpenTelemetry.

Một số thông tin cơ bản hơn về OpenTelemetry

Chúng ta cũng hãy làm sáng tỏ một chút về dự án OpenTelemetry, được hình thành bằng cách sáp nhập MởTheo Dõi Mở điều tra dân số dự án.


OpenTelemetry hiện cung cấp một loạt các thành phần toàn diện dựa trên một tiêu chuẩn xác định một bộ API, SDK và công cụ cho nhiều ngôn ngữ lập trình khác nhau và mục tiêu chính của dự án là tạo, thu thập, quản lý và xuất dữ liệu.


Tuy nhiên, OpenTelemetry không cung cấp nền tảng lưu trữ dữ liệu hoặc công cụ trực quan hóa.


Vì chúng tôi chỉ quan tâm đến việc theo dõi, chúng tôi đã khám phá các giải pháp nguồn mở phổ biến nhất để lưu trữ và trực quan hóa các dấu vết:

  • Jaeger
  • Zipkin
  • Thời gian Grafana


Cuối cùng, chúng tôi đã chọn Grafana Tempo vì khả năng trực quan hóa ấn tượng, tốc độ phát triển nhanh và tích hợp với thiết lập Grafana hiện tại của chúng tôi để trực quan hóa số liệu. Việc có một công cụ thống nhất duy nhất cũng là một lợi thế đáng kể.

Các thành phần OpenTelemetry

Chúng ta hãy cùng phân tích một chút các thành phần của OpenTelemetry.


Đặc điểm kỹ thuật:

  • API — các loại dữ liệu, hoạt động, enum

  • SDK — triển khai thông số kỹ thuật, API trên các ngôn ngữ lập trình khác nhau. Một ngôn ngữ khác nhau có nghĩa là trạng thái SDK khác nhau, từ alpha đến ổn định.

  • Giao thức dữ liệu (OTLP) và quy ước ngữ nghĩa


Java API SDK:

  • Thư viện công cụ mã hóa
  • Công cụ xuất khẩu — công cụ để xuất các dấu vết đã tạo ra sang phần phụ trợ
  • Cross Service Propagators — một công cụ để chuyển ngữ cảnh thực thi ra bên ngoài quy trình (JVM)


OpenTelemetry Collector là một thành phần quan trọng, một proxy tiếp nhận dữ liệu, xử lý và truyền dữ liệu – chúng ta hãy xem xét kỹ hơn.

Bộ sưu tập OpenTelemetry

Đối với các hệ thống tải cao xử lý hàng nghìn yêu cầu mỗi giây, việc quản lý khối lượng dữ liệu là rất quan trọng. Dữ liệu theo dõi thường vượt quá dữ liệu kinh doanh về khối lượng, khiến việc ưu tiên dữ liệu nào cần thu thập và lưu trữ trở nên cần thiết. Đây là lúc công cụ xử lý và lọc dữ liệu của chúng tôi phát huy tác dụng và cho phép bạn xác định dữ liệu nào đáng lưu trữ. Thông thường, các nhóm muốn lưu trữ các dấu vết đáp ứng các tiêu chí cụ thể, chẳng hạn như:


  • Các dấu vết có thời gian phản hồi vượt quá ngưỡng nhất định.
  • Các dấu vết gặp lỗi trong quá trình xử lý.
  • Các dấu vết chứa các thuộc tính cụ thể, chẳng hạn như các dấu vết đi qua một dịch vụ vi mô nhất định hoặc được đánh dấu là đáng ngờ trong mã.
  • Lựa chọn ngẫu nhiên các dấu vết thường xuyên cung cấp ảnh chụp nhanh thống kê về hoạt động bình thường của hệ thống, giúp bạn hiểu được hành vi điển hình và xác định xu hướng.

Sau đây là hai phương pháp lấy mẫu chính được sử dụng để xác định dấu vết nào cần lưu và dấu vết nào cần loại bỏ:

  • Lấy mẫu đầu — quyết định khi bắt đầu theo dõi có nên giữ lại hay không
  • Lấy mẫu đuôi — chỉ quyết định sau khi có dấu vết đầy đủ. Điều này là cần thiết khi quyết định phụ thuộc vào dữ liệu xuất hiện sau trong dấu vết. Ví dụ, dữ liệu bao gồm các khoảng lỗi. Những trường hợp này không thể xử lý bằng cách lấy mẫu đầu vì chúng yêu cầu phân tích toàn bộ dấu vết trước


OpenTelemetry Collector giúp cấu hình hệ thống thu thập dữ liệu để nó chỉ lưu dữ liệu cần thiết. Chúng ta sẽ thảo luận về cấu hình của nó sau, nhưng bây giờ, hãy chuyển sang câu hỏi về những gì cần thay đổi trong mã để nó bắt đầu tạo dấu vết.

Thiết bị đo lường không mã

Việc tạo dấu vết thực sự cần ít mã hóa – chỉ cần khởi chạy ứng dụng của chúng tôi bằng java-agent, chỉ định cấu hình :


-javaagent:/opentelemetry-javaagent-1.29.0.jar

-Dotel.javaagent.configuration-file=/otel-config.properties


OpenTelemetry hỗ trợ một số lượng lớn thư viện và khung , vì vậy sau khi khởi chạy ứng dụng với tác nhân, chúng tôi ngay lập tức nhận được dấu vết có dữ liệu về các giai đoạn xử lý yêu cầu giữa các dịch vụ, trong DBMS, v.v.


Trong cấu hình tác nhân của chúng tôi, chúng tôi đã vô hiệu hóa các thư viện mà chúng tôi đang sử dụng có các khoảng thời gian mà chúng tôi không muốn thấy trong các dấu vết và để có được dữ liệu về cách mã của chúng tôi hoạt động, chúng tôi đã đánh dấu nó bằng chú thích :


 @WithSpan("acquire locks") public CompletableFuture<Lock> acquire(SortedSet<Object> source) { var traceLocks = source.stream().map(Object::toString).collect(joining(", ")); Span.current().setAttribute("locks", traceLocks); return CompletableFuture.supplyAsync(() -> /* async job */); }


Trong ví dụ này, chú thích @WithSpan được sử dụng cho phương thức, chú thích này báo hiệu nhu cầu tạo một khoảng thời gian mới có tên là " acquire locks " và thuộc tính " locks " được thêm vào khoảng thời gian đã tạo trong thân phương thức.


Khi phương thức hoàn tất hoạt động, khoảng thời gian được đóng lại và điều quan trọng là phải chú ý đến chi tiết này đối với mã không đồng bộ. Nếu bạn cần lấy dữ liệu liên quan đến công việc của mã không đồng bộ trong các hàm lambda được gọi từ một phương thức có chú thích, bạn cần tách các lambda này thành các phương thức riêng biệt và đánh dấu chúng bằng một chú thích bổ sung.

Thiết lập thu thập dấu vết của chúng tôi

Bây giờ, chúng ta hãy nói về cách cấu hình toàn bộ hệ thống thu thập dấu vết. Tất cả các ứng dụng JVM của chúng tôi đều được khởi chạy bằng một tác nhân Java gửi dữ liệu đến trình thu thập OpenTelemetry.


Tuy nhiên, một bộ thu thập đơn lẻ không thể xử lý luồng dữ liệu lớn và phần này của hệ thống phải được mở rộng. Nếu bạn khởi chạy một bộ thu thập riêng cho mỗi ứng dụng JVM, việc lấy mẫu đuôi sẽ bị hỏng, vì phân tích dấu vết phải diễn ra trên một bộ thu thập và nếu yêu cầu đi qua nhiều JVM, các khoảng của một dấu vết sẽ kết thúc trên các bộ thu thập khác nhau và việc phân tích của chúng sẽ không thể thực hiện được.


Ở đây, một bộ sưu tập được cấu hình khi một người giữ thăng bằng đến giải cứu.


Kết quả là, chúng ta có hệ thống sau: Mỗi ứng dụng JVM gửi dữ liệu đến cùng một bộ thu thập cân bằng, có nhiệm vụ duy nhất là phân phối dữ liệu nhận được từ các ứng dụng khác nhau, nhưng liên quan đến một dấu vết nhất định, đến cùng một bộ thu thập-bộ xử lý. Sau đó, bộ thu thập-bộ xử lý gửi dữ liệu đến Grafana Tempo.



Chúng ta hãy xem xét kỹ hơn cấu hình của các thành phần trong hệ thống này.

Bộ thu thập cân bằng tải

Trong cấu hình bộ thu-cân bằng, chúng tôi đã cấu hình các bộ phận chính sau:


 receivers:  otlp:    protocols:      grpc: exporters:  loadbalancing:    protocol:      otlp:        tls:          insecure: true    resolver:      static:        hostnames:          - collector-1.example.com:4317          - collector-2.example.com:4317          - collector-3.example.com:4317 service:  pipelines:    traces:      receivers: [otlp]      exporters: [loadbalancing]


  • Bộ thu — nơi các phương pháp (mà dữ liệu có thể được bộ thu thập nhận) được cấu hình. Chúng tôi đã cấu hình việc tiếp nhận dữ liệu chỉ theo định dạng OTLP. (Có thể cấu hình việc tiếp nhận dữ liệu qua nhiều giao thức khác (ví dụ như Zipkin, Jaeger.)
  • Exporters — phần cấu hình nơi cân bằng dữ liệu được cấu hình. Trong số các bộ thu thập-bộ xử lý được chỉ định trong phần này, dữ liệu được phân phối tùy thuộc vào hàm băm được tính toán từ mã định danh theo dõi.
  • Phần Dịch vụ chỉ định cấu hình về cách dịch vụ sẽ hoạt động: chỉ với dấu vết, sử dụng bộ thu OTLP được cấu hình ở trên cùng và truyền dữ liệu như một bộ cân bằng, tức là không xử lý.

Người thu thập dữ liệu với xử lý dữ liệu

Cấu hình của bộ thu thập-bộ xử lý phức tạp hơn, vì vậy chúng ta hãy xem xét ở đây:


 receivers:  otlp:    protocols:      grpc:        endpoint: 0.0.0.0:14317 processors:  tail_sampling:    decision_wait: 10s    num_traces: 100    expected_new_traces_per_sec: 10    policies:      [          {            name: latency500-policy,            type: latency,            latency: {threshold_ms: 500}          },          {            name: error-policy,            type: string_attribute,            string_attribute: {key: error, values: [true, True]}          },          {            name: probabilistic10-policy,            type: probabilistic,            probabilistic: {sampling_percentage: 10}          }      ]  resource/delete:    attributes:      - key: process.command_line        action: delete      - key: process.executable.path        action: delete      - key: process.pid        action: delete      - key: process.runtime.description        action: delete      - key: process.runtime.name        action: delete      - key: process.runtime.version        action: delete exporters:  otlp:    endpoint: tempo:4317    tls:      insecure: true service:  pipelines:    traces:      receivers: [otlp]      exporters: [otlp]


Tương tự như cấu hình bộ thu thập-cân bằng, cấu hình xử lý bao gồm các phần Receivers, Exporters và Service. Tuy nhiên, chúng ta sẽ tập trung vào phần Processors, phần này giải thích cách dữ liệu được xử lý.


Đầu tiên, phần tail_sampling trình bày một cấu hình cho phép lọc dữ liệu cần thiết để lưu trữ và phân tích:


  • latency500-policy : quy tắc này chọn các dấu vết có độ trễ vượt quá 500 mili giây.

  • error-policy : quy tắc này chọn các dấu vết gặp lỗi trong quá trình xử lý. Nó tìm kiếm một thuộc tính chuỗi có tên "error" với các giá trị "true" hoặc "True" trong khoảng dấu vết.

  • probabilistic10-policy : quy tắc này chọn ngẫu nhiên 10% trong số tất cả các dấu vết để cung cấp thông tin chi tiết về hoạt động ứng dụng bình thường, lỗi và quá trình xử lý yêu cầu dài.


Ngoài tail_sampling, ví dụ này hiển thị phần resource/delete để xóa các thuộc tính không cần thiết cho việc phân tích và lưu trữ dữ liệu.

Kết quả

Cửa sổ tìm kiếm theo dõi Grafana kết quả cho phép bạn lọc dữ liệu theo nhiều tiêu chí khác nhau. Trong ví dụ này, chúng tôi chỉ hiển thị danh sách các theo dõi nhận được từ dịch vụ sảnh, nơi xử lý siêu dữ liệu trò chơi. Cấu hình cho phép lọc trong tương lai theo các thuộc tính như độ trễ, lỗi và lấy mẫu ngẫu nhiên.


Cửa sổ theo dõi hiển thị dòng thời gian thực hiện của dịch vụ tiền sảnh, bao gồm nhiều khoảng thời gian khác nhau tạo nên yêu cầu.


Như bạn có thể thấy trong hình, trình tự các sự kiện diễn ra như sau — khóa được lấy, sau đó các đối tượng được lấy từ bộ nhớ đệm, tiếp theo là thực hiện giao dịch xử lý các yêu cầu, sau đó các đối tượng được lưu trữ lại trong bộ nhớ đệm và khóa được giải phóng.


Các khoảng liên quan đến yêu cầu cơ sở dữ liệu được tạo tự động do công cụ của các thư viện chuẩn. Ngược lại, các khoảng liên quan đến quản lý khóa, hoạt động bộ nhớ đệm và khởi tạo giao dịch được thêm thủ công vào mã nghiệp vụ bằng cách sử dụng các chú thích đã đề cập ở trên.



Khi xem một khoảng, bạn có thể thấy các thuộc tính cho phép bạn hiểu rõ hơn những gì đã xảy ra trong quá trình xử lý, ví dụ, xem truy vấn trong cơ sở dữ liệu.



Một trong những tính năng thú vị của Grafana Tempo là đồ thị dịch vụ , hiển thị đồ họa tất cả các dịch vụ xuất dấu vết, các kết nối giữa chúng, tốc độ và độ trễ của các yêu cầu:


Kết thúc

Như chúng ta đã thấy, làm việc với OpenTelemetry tracing đã cải thiện khả năng quan sát của chúng tôi khá tốt. Với những thay đổi tối thiểu về mã và thiết lập bộ thu thập được cấu trúc tốt, chúng tôi đã có được những hiểu biết sâu sắc – cộng với việc chúng tôi thấy khả năng trực quan hóa của Grafana Tempo đã bổ sung thêm cho thiết lập của chúng tôi như thế nào. Cảm ơn vì đã đọc!