Rasa sakit dan penderitaan saat men-debug layanan mikro dalam pengembangan web

Di IT, Anda jarang melihat orang yang belum pernah mendengar tentang layanan microser. Ada banyak artikel di Internet dan di situs-situs khusus mengenai hal ini yang secara umum menjelaskan perbedaan antara monolit dan, pada kenyataannya, layanan microser dengan baik. Pengembang Java yang tidak berpengalaman, setelah membaca artikel dari kategori "Apa itu layanan microser untuk aplikasi web dan apa yang mereka makan", penuh dengan kegembiraan dan keyakinan bahwa sekarang semuanya akan menjadi luar biasa. Setelah semua, tujuan utama adalah untuk "melihat" monolit mengerikan (artefak terakhir, yang, sebagai aturan, adalah file perang / telinga), yang melakukan banyak hal, pada sejumlah layanan yang hidup terpisah, masing-masing akan melakukan fungsi yang didefinisikan secara ketat yang hanya terkait dengannya, dan akan melakukannya dengan baik. Selain ini datang skalabilitas horisontal - hanya melakukan penskalaannode yang sesuai, dan semuanya akan menjadi besar. Semakin banyak pengguna telah tiba atau dibutuhkan lebih banyak kapasitas - baru ditambahkan 5-10 instance layanan baru. Secara umum, ini adalah cara kerjanya, tetapi, seperti yang Anda ketahui, iblis ada dalam rinciannya, dan apa yang awalnya tampak cukup sederhana, setelah diperiksa lebih dekat, dapat berubah menjadi masalah yang tidak ada yang memperhitungkan awalnya.

Dalam posting ini, rekan-rekan dari praktik Java Rexoft berbagi pengalaman mereka tentang cara men-debug layanan microser untuk web.



Bagaimana cara mencapai integritas data transaksional


Ketika mencoba mentransfer arsitektur dari monolit ke layanan mikro, tim yang tidak memiliki pengalaman seperti itu sebelumnya sering mulai membagi layanan menjadi objek tingkat atas dari model domain, misalnya: Pengguna / Klien / Karyawan , dll. Di masa depan, dengan studi yang lebih rinci, pemahaman muncul. yang lebih nyaman untuk memecah blok yang lebih besar yang menggabungkan beberapa objek dari domain domain di dalamnya. Karena ini, Anda dapat menghindari panggilan yang tidak perlu ke layanan pihak ketiga.

Poin penting kedua adalah dukungan untuk integritas data transaksional. Dalam monolit, masalah ini diselesaikan melalui Server Aplikasi, di mana perang / telinga berputar, di mana wadah, pada kenyataannya, menguraikan batas-batas transaksi. Dalam hal layanan microser, batas-batas transaksi kabur dan ada kebutuhan, selain menulis kode logika bisnis, untuk dapat mengelola integritas data dan menjaga konsistensi mereka di antara berbagai bagian sistem. Ini adalah tugas yang tidak sepele. Rekomendasi untuk menyelesaikan masalah arsitektur semacam ini dapat ditemukan di Internet dan di komunitas teknis yang relevan.

Dalam artikel ini, kami akan mencoba menggambarkan kesulitan teknis tertentu yang muncul ketika tim mencoba bekerja dengan layanan mikro, dan cara untuk menyelesaikannya. Saya segera mencatat bahwa opsi yang diusulkan bukan satu-satunya yang benar. Mungkin ada layanan yang lebih elegan, tetapi rekomendasi yang akan saya berikan diuji dalam praktik dan secara tepat menyelesaikan kesulitan yang ada, dan apakah menggunakannya atau tidak adalah masalah pribadi bagi semua orang.

Masalah utama dengan microservices adalah bahwa mereka sangat mudah untuk menjalankan secara lokal (misalnya, menggunakan spring.io dan intellij ide , ini dapat dilakukan hanya dalam 5 menit, atau bahkan kurang). Namun, ketika mencoba melakukan hal yang sama di Kubernetescluster (jika Anda memiliki sedikit pengalaman dengan itu sebelumnya), peluncuran sederhana dari pengontrol yang mencetak "Hello World" ketika mengakses titik akhir tertentu mungkin memakan waktu setengah hari. Dalam kasus monolit, situasinya lebih sederhana. Setiap pengembang memiliki Server Aplikasi lokal. Proses penyebaran juga cukup sederhana - Anda perlu menyalin artefak perang / telinga terakhir ke tempat yang tepat di Server Aplikasi secara manual atau menggunakan IDE . Biasanya ini bukan masalah.

Nuansa Debugging


Poin penting kedua adalah debugging . Dalam situasi dengan monolit, diasumsikan bahwa pengembang memiliki Server Aplikasi pada mesinnya, yang digunakan untuk perang / pendengarannya. Anda selalu dapat men-debug, karena semua yang Anda butuhkan sudah dekat. Dengan microservices, semuanya sedikit lebih rumit, layanan biasanya merupakan hal itu sendiri. Sebagai aturan, ia memiliki skema basis datanya sendiri, di mana datanya berada, melakukan fungsi spesifik khusus untuknya, semua komunikasi dengan layanan lain diatur melalui panggilan HTTP sinkron (mis. Via RestTemplate atau Feign), tidak sinkron (mis. Kafka atau RabbitMQ). Oleh karena itu, pada dasarnya tugas sederhana untuk menyimpan atau memvalidasi objek tertentu yang sebelumnya dilaksanakan di satu tempat, di dalam satu file perang / telinga, dalam kasus umum dengan pendekatan layanan-mikro, menjadi dapat direpresentasikan dalam bentuk: pergi ke satu atau N layanan yang berdekatan, baik itu operasi akuisisi data , misalnya, beberapa nilai referensi, atau operasi penyelamatan entitas yang berdekatan,yang datanya diperlukan untuk melakukan logika bisnis dalam layanan kami. Menulis logika bisnis dalam hal ini menjadi jauh lebih sulit.

Dengan demikian, opsi solusinya adalah sebagai berikut :

  1. Tulis kode logika bisnis Anda. Pada saat yang sama, semua panggilan eksternal diejek - kontrak eksternal ditiru, tes ditulis dengan asumsi bahwa kontrak eksternal sama seperti itu, setelah itu ada penyebaran ke sirkuit untuk verifikasi. Terkadang beruntung, dan integrasi langsung bekerja, kadang tidak beruntung - Anda harus mengulang kode logika bisnis beberapa kali, karena selama waktu kami mengimplementasikan fungsionalitasnya, kode di layanan yang berdekatan diperbarui, tanda tangan API berubah dan kami perlu mengulanginya bagian dari tugas ada di sisinya.
  2. . , , Kubernetes, . . , โ€” , remote debug . , runtime , , . -, , 2โ€“5 , . . , Kubernetes , . -, (Per thread), , .

Kubernetes


Solusi untuk masalah ini, pada kenyataannya, adalah telepresence . Mungkin ada program lain dari jenis ini, tetapi pengalaman pribadi hanya bersamanya, dan dia memantapkan dirinya secara positif. Secara umum, prinsip operasi adalah sebagai berikut:

Pada mesin lokal, pengembang menginstal telepresenc e, mengkonfigurasi kubectl untuk mengakses cluster Kubernet yang sesuai (menambahkan konfigurasi loop ke ~ / .kube / config ). Setelah itu, telepresence dimulai , yang sebenarnya bertindak sebagai proksi antara komputer pengembang lokal dan Kubernetes. Ada beberapa opsi peluncuran, lebih baik untuk melihat lebih detail dalam panduan resmi, tetapi dalam kasus paling mendasar, ada dua langkah:

  1. Sudo telepresence (, Linux- , sudo . , root/). Kubernetes deployment telepresence . deployment Kubernetes.
  2. Memulai instance layanan Anda seperti biasa di komputer lokal pengembang. Namun, dalam hal ini, ia akan memiliki akses ke seluruh infrastruktur kluster Kubernetes, baik itu Service Discovery (Eureka, Consul), Api Gateway (Zuul), Kafka dan antriannya, jika ada, dan sebagainya. Faktanya, kita memiliki akses ke semua lingkungan cluster yang kita butuhkan, tetapi secara lokal. Bonusnya adalah kemungkinan debugging lokal, tetapi dalam lingkungan cluster, dan itu sudah akan jauh lebih cepat, karena kita, pada kenyataannya, berada di dalam Kubernetes (melalui terowongan), dan tidak mengaksesnya dari luar melalui port untuk debug jarak jauh.

Solusi ini memiliki beberapa kelemahan:

  1. Telepresence Linux Mac, Windows VFS, , issue GitHub. . , - Linux/Mac, .
  2. , Service Discovery (Eureka, Consul) โ€” Round Robin , endpoint , , , :

  • kubernetes -> . telepresence deployment , ยซยป Eureka ip-address:port/service-name dns-name:port/service-name , . . Kubernetes , timeout;
  • deployment - Kubernetes , ( ) (Round Robin), ;
  • endpoint, feature, HTTP 404 endpoint Gateway, Service Discovery , Round Robin . Service Discovery endpoint , HTTP 404.
  • , , .


Dengan perutean dinamis permintaan, kami bermaksud bahwa API Gateway (Zuul) memiliki kemampuan untuk memilih di antara beberapa contoh layanan yang sama yang kami butuhkan. Dalam kasus umum, masalah ini dapat diselesaikan dengan menambahkan predikat yang memungkinkan Anda untuk memilih layanan yang diinginkan dari kumpulan layanan umum dengan nama yang sama pada tahap pemrosesan permintaan. Tentu saja, setiap layanan di antara mereka yang ingin kami dapat rute secara dinamis, harus memiliki beberapa jenis meta-informasi yang berisi data yang akan digunakan untuk menentukan apakah layanan ini diperlukan atau tidak. Spring Cloud (dalam kasus Eureka), misalnya, memungkinkan Anda melakukan ini dengan menentukan di blok metadata khusus di application.yml :

eureka:
  instance:
    preferIpAddress: true
    metadata-map:
      service.label: develop

Setelah mendaftarkan layanan tersebut di Service Discovery di com.netflix.appinfo.InstanceInfo # getMetadata akan ada label dengan service.label utama dan nilai yang dikembangkan , yang dapat diperoleh dalam runtime. Poin penting pada tahap memulai layanan adalah memeriksa apakah instance layanan ada di Service Discovery dengan meta-informasi seperti itu atau tidak untuk menghindari kemungkinan tabrakan.

Opsi Perutean


Setelah itu, solusi masalah dapat dikurangi menjadi dua opsi:

  1. API Gateway . , , , , Headers: DestionationService: feature/PRJ-001. , , Header . , โ€” - API Gateway.
  2. API Gateway, , . ., , , Zuul 1 endpoint- /api/users/โ€ฆ user, feature/PRJ-001, Zuul 2 endpoint- /api/users/โ€ฆ user, feature/PRJ-002. , N API Gateway N , . . , . . feature โ€” , , , , , , . API Gateway, , . ., , , โ€” , .






Sebagai bagian dari API Gateway, juga bermanfaat untuk menyediakan mekanisme yang memungkinkan Anda untuk dapat mengubah aturan perutean pada saat runtime. Yang terbaik adalah menempatkan pengaturan ini di config-map . Dalam hal ini, itu akan cukup untuk menulis ulang rute baru dan me-restart Gateway API di Kubernetes untuk memperbarui routing, atau menggunakan Spring Boot Actuator (asalkan ada ketergantungan yang sesuai di Gateway API) - panggilan endpoint / refresh, yang pada dasarnya membaca ulang data dari config-map dan akan memperbarui rute.

Poin penting adalah bahwa harus ada, secara relatif, contoh turunan dari layanan (misalnya, berlabel pengembangan, yang akan dikumpulkan dari cabang utama pengembangan layanan) dan API Gateway utama yang terpisah, yang akan selalu ditentukan dalam pengaturan yang akan mengakses layanan ini. Intinya, kami menyediakan lingkungan pementasan independen yang akan selalu operasional dalam konteks rute dinamis.

Contoh blok konfigurasi peta untuk Gateway API yang berisi pengaturan untuk perutean (di sini ini hanya contoh bagaimana tampilannya, untuk operasi yang tepat memerlukan ikatan yang sesuai dalam bentuk kode di sisi belakang layanan API Gateway) :

{
  "kind": "ConfigMap",
  "apiVersion": "v1",
  "metadata": {
    ...
  },  
"data": {
    ...        
    "rules.meta.user": "develop",
    "rules.meta.client": "develop",
    "rules.meta.notification": "feature/PRJ-010",
    ...    
  }
}

rules.meta adalah peta yang berisi aturan perutean untuk layanan.
pengguna / klien / pemberitahuan - nama layanan yang terdaftar di Eureka.

mengembangkan / fitur / PRJ-010 - label layanan dari application.yml dari layanan yang sesuai, berdasarkan mana layanan yang diinginkan akan dipilih di antara semua layanan yang tersedia dengan nama yang sama dari Service Discovery , jika ada lebih dari satu contoh layanan tersebut.

Kesimpulan


Seperti segala sesuatu di dunia ini, alat dan solusi di bidang TI tidak sempurna. Jangan berpikir bahwa jika Anda mengubah arsitektur, semua masalah akan berakhir sekaligus. Hanya perendaman terperinci dalam teknologi yang digunakan dan pengalaman Anda sendiri akan memberi Anda gambaran nyata tentang apa yang terjadi.

Saya harap materi ini membantu Anda memecahkan masalah Anda. Tugas menarik dan jual tanpa bug!

All Articles