1. Kirish
Ushbu loyihada kemalarning sensor ma'lumotlaridagi anomaliyalarning aniqlanishi va ogohlantiruvchi signallarni berish funksiyasini joriy etish zarur edi. Buni amalga oshirish uchun har bir kema uchun sensor ma'lumotlarini tekshirib, ogohlantirish berish shartlarini aniqlab, ogohlantiruvchi signal beruvchi yangi ish rejasini ishlab chiqdik.
Bu rivojlantirish muhitining Kubernetes klasterida Pod shaklida tarqatilgan xizmatda amalga oshirildi va kuniga bir marta ishga tushirilishi va kemalar bo'yicha ogohlantirish berish statistik ma'lumotlarini to'plashi kutilgan edi. Biroq, kutilganidan farqli ravishda, logika bajargan paytda Podning qayta ishga tushishi muammosi takroran yuzaga keldi.
2. Muammo holati
Logika har ishga tushganda Podning qayta ishga tushish sabablarini aniqlashga qaror qildik va OOMKilled (Out Of Memory Killed) ya'ni konteyner xotira cheklovini oshirib, OOM Killer tomonidan majburan tugatilayotganini ko'rdik. Kemaning katta hajmdagi ma'lumotlarini qayta ishlash uchun Memory Limit sozlamasi juda kichikmi deb o'ylab, Podning Memory Limitini oshirishga harakat qildik, lekin muammo hal bo'lmaganligi davom etdi.
Natijada Podning qayta ishga tushish holati OOMKilled aniq sabab bo'lgan bo'lsa-da, cheklov sozlamalari bilan bevosita bog'liq emas edi. Memory Limit yetarli emas edi, lekin nega Pod OOMKilled tufayli majburan tugatilgan?
3. JVM xotirasi: Heap va Non-Heap
JVM xotirasini katta miqdorda Heap va Non-Heap sohalariga bo'lish mumkin.
3.1 Heap sohasi
Java ob'ektlari saqlanadigan xotira sohasidir, Java dasturida new kalit so'zi yordamida ob'ekt yaratilsa, ko'pchilik Heap sohasiga saqlanadi. Bu GC (Garbage Collector) ning asosiy boshqaruv ob'ekti bo'lib, endi isbotlanmagan ob'ektlar GC tomonidan tozalanadi.
Heap yana katta miqdorda Young Generation va Old Generation ga bo'linadi.
1) Young Generation
Yangi yaratilgan ob'ektlarning birinchi marta saqlanadigan sohasidir. Ko'p ob'ektlar yaratilgandan so'ng qisqa vaqt ichida foydalaniladi. Misol uchun, talablarni qayta ishlash vaqtida vaqtinchalik yaratilgan DTO, satrlar, to'plamlar shular jumlasidandir.
Young Generation Eden va Survivor sohalariga bo'linishi mumkin, ob'ektlar odatda birinchi marta Eden sohasida yaratiladi. Eden sohasi to'lib qolsa, Minor GC yuz beradi va Minor GC dan so'ng omon qolgan ob'ektlar Survivor sohasiga o'tadi.
Bir necha marta Minor GC dan keyin hamon omon qolgan ob'ektlar uzoq muddatli omon qoladigan ob'ektlar deb hisoblanadi va Old Generation ga o'tadi, bu jarayonni targ'ibot (Promotion) deb ataladi.
2) Eski avlod
Uzoq muddat saqlanayotgan obyektlar saqlanadigan soha. Spring Bean, kesh ma'lumotlari, bir necha martalab Minor GCdan o'tgan obyektlar va boshqalar Old Generationda qolishi mumkin.
Agar bu soha etishmay qolsa, Full GC yoki Major GC yuz berishi mumkin. Full GC Yangi avlod va Eski avlodni keng ko'zdan kechirish kerak bo'ladi, shuning uchun Minor GCdan og'irroq va ilova vaqtincha to'xtatiladigan Stop-The-World vaqti ko'proq bo'lishi mumkin.
3.2 Non-Heap soha
Java obyektlari saqlanadigan Heapdan tashqari JVM xotira sohasi bo'lib, bu soha GCning asosiy maqsadi bo'ladigan oddiy obyekt saqlash joyi emas, lekin JVM Java ilovasini bajarish uchun zarur bo'lgan xotira. Metaspace, Code Cache, Thread Stack, JVM Native Memory kabi ob'ektlar Non-Heap sohasiga kiradi.
4. Heap hajmi va GC o'rtasidagi bog'lanish
JVMning Heap hajmi GC ish mexanizmi bilan yaqqol bog'liq. Ilovada yaratilgan obyektlarning aksariyati Heapda saqlanadi va GC bu Heap sohasini maqsad qilib olib, endi murojaat qilinmayotgan obyektlarni tozalab, xotirani qaytaradi. Shuning uchun Heap hajmi qanday belgilanganiga qarab, GC harakatga kelish vaqti, chastotasi, bajarish vaqtlari farq qilishi mumkin.
4.1 Heap hajmi kichik qilib belgilanganda
Heap hajmi kichik qilib belgilanganida, obyektlarni saqlash uchun joy tezgah etishmaydi va JVM tez-tez GCni o'tkazishga majbur bo'ladi. Xususan, Yangi avlodning Eden sohasi tezda to'lib qolganda Minor GC tez-tez sodir bo'ladi va Eski avlodda qulay joy bulsa, Full GC yuz berishi mumkin. Agar Heap kichik bo'lsa, GC tez-tez yuz beradi, ammo bir marta GCda tekshirilishi va tozalanishi kerak bo'lgan xotira maydoni nisbatan kichik, shuning uchun GCni o'tkazish vaqti qisqa bo'lishi mumkin.
4.2 Heap hajmi katta qilib belgilanganda
Boshqa tomondan, Heap hajmi katta qilib belgilanganida, ko'proq obyektlarni saqlash mumkin, shuning uchun GC yuz berish chastotasi kamayishi mumkin. Xususan, Eski avlod sohasi katta bo'lsa, uzoq vaqt davomida yashab qolgan obyektlar ko'p to'planishi mumkin, lekin joy etishmovchiligiga asta-sekin yetib kelinadi, buning natijasida Full GC tez-tez yuz bermaydi. Shunda, agar Heap katta bo'lsa, Full GC yuz bersa, tozalanishi kerak bo'lgan xotira maydoni katta bo'ladi va bir marta Full GC o'tkazish vaqi o'zgarishi mumkin. Shuningdek, bir nechta GC Threadlardan foydalangan holda parallel ishlash uchun Parallel GCdan foydalangan taqdirda ham, Full GC davomida ilova Threadi to'xtab qoladigan Stop-The-World harakatini yuz berishi mumkin.
Heapni katta qilib belgilash Full GC yuz berish chastotasini kamaytirishi mumkin, lekin GC keyinroq yuz berishi davomida keraksiz obyektlar Heapda uzoq muddat qolib ketishi mumkin. Va Full GC yuz berishda katta xotira maydoni tozalanishi kerak bo'lganligi sababli, GC vaqti uzoq bo'lishi mumkin. Boshqa tomondan, Heapni juda kichik qilib belgilash GCning juda tez-tez yuz berishiga olib kelishi mumkin, bu esa ilovaning ish bajarish samaradorligini pasaytirishi mumkin. Shuning uchun, Heap hajmi kichik yoki katta bo'lishi yaxshi emas, ilova xolatiga qarab ma'qul ravishda sozlash zarur.
5. Muammo Podning JVM ishga tushirish imkoniyatlari
Podning xotira limiti 2GB edi va JVM ishga tushirish imkoniyatlari -XX:MaxRAMPercentage=75 bo'lib, bu konteyner xotira asosida Heap ning maksimal ulushini 75% ga belgiladi. Oddiy hisob-kitob asosida 2GB ning 75% i taxminan 1.5GB gacha Heap o'sishi mumkin va qolgan taxminan 0.5GB Non-Heap va Native sohalarida foydalanishga mo'ljallangan. Kubernetes muhitida bu nisbatan yuqori darajadagi Heap ulushi bo'lib, Heap sohasining zarur bo'lmagan darajada katta belgilanib, Eski avlod sohasida nisbatan katta tahminlanganligi natijasida Full GC yuz berish chastotasi pasayib, OOMKilled sodir bo'lganligiga shubha qilingan edi.
Shuningdek, -XX:MaxRAMPercentage=60 ga kamaytirilgandan so'ng, kunlik partiyaviy mantiqni ishga tushirish va Pod holatini monitoring qilish natijasida Pod qayta ishga tushirilmasdan mantiq normal ishga tushirilishini tasdiqladik. JVMning maksimal xotirasini, ajratilgan xotira ichidagi bo'sh xotira, real ishlatilayotgan xotirani ilova loglaridan tasdiqlaganimizda, oldingisiga qaraganda Full GCning normal tarzda amalga oshayotganini ko'rdik. -XX:MaxRAMPercentage=75 bo'lganda, mantiq ishga tushirish davomida ishlatilayotgan xotira maksimal xotiraga yetganda GC yuz bermasdan Pod qayta ishga tushish holatlari bo'lgan, lekin -XX:MaxRAMPercentage=60 ga kamaytirilgandan so'ng, ilova loglarini tekshirganda, ishlatilayotgan xotira maksimal xotiraga yetganda Full GC amalga oshirilishi natijasida ishlatilayotgan xotira aniqlik bilan pastga tushib ketganligini ko'rdik.
6. Tugatish
Ushbu misol orqali Kubernetes Podning OOMKilled bilan bog'liq muammosi faqatgina Memory Limitni belgilash orqali hal qilinmaydi, degan xulosaga keldik. Albatta, mos Memory Limitni belgilash muhimroq, lekin JVM Heap Hajmini belgilashni ham albatta hisobga olish kerakligini yangi tushundik.
Shu sababli, biz Kubernetes muhitida Java ilovasini ishga tushirayotganimizda OOMKilled muammosi bilan duch kelsak, faqat Memory Limitni oshirish o'rniga, Heap Hajmi va Non-Heap mayonining to'g'ri belgilanganligini, GCning to'g'ri kuzatilayotganini ham birga tekshirishimiz kerak. Buning yordamida keraksiz resurs sarfini kamaytirish va yanada barqaror va samarali ilova ishga tushirish muhitini yaratishimiz mumkin.
deeenee