Quartz Scheduler foydalanish tajribasi

Quartz Scheduler foydalanish tajribasi

1. Kirish: Nima uchun taqsimlangan jadvalga ehtiyoj bor

Vizend da ilgari har bir xizmat alohida jadvalni boshqarish bilan shug'ullanardi. Ushbu tuzilma oddiy muhitlarda muammo tug'dirmasa ham, xizmat kengayganida bir xil ish bir necha instansiyada takrorlanishi, nosozliklar paytida jadvalning yo'qolishi, tiklov mumkin bo'lmagan muammolari, va har bir xizmat uchun jadval mantiqining tarqalishi kabi murakkabliklar kelib chiqardi.

Ushbu muammoni hal qilish uchun vaqtga asoslangan vazifalarni bajarish bilan shug'ullanadigan rejalashtirish mikroservisini ishlab chiqdik. Boshqa xizmatlar "muayyan vaqtda hodisa yuzaga keltiring" yoki "muayyan vaqtda API ni chaqiring" kabi talablar yuborganda, o'sha vaqtda aniq amalga oshiradi. Belgilangan vaqtda Kafka mavzusiga hodisa yuborish yoki tashqi HTTP API ni chaqirish mumkin. Shuningdek, takrorlanuvchi rejalashtirish (sekund/daqiqa/soat/kun/hafta/oy/yil bo'yicha) va bir martalik rejalashtirishni qo'llab-quvvatlaydi. Masalan, "har kuni soat 9:00 da billing eslatmasi hodisasini yuboring" yoki "2024-yil 31-dekabrda tungi 12:00 da bir martalik hisoblash API ni chaqiring" kabi. Shuningdek, dinamik rejalashtirishni ro'yxatga olish/yangilash/o'chirish (REST API asosida), tarqatilgan muhitda takrorlanmasdan yagona bajarilishni ta'minlash, nosozlik yuz berganda avtomatik tiklanishni qo'llab-quvvatlash maqsadida ishlab chiqildi.

2. Texnologiyani tanlash: Nima uchun Quartz JDBC klasterlash?

Springning @ScheduledAnnotatsiyani ishlatishga asoslangan oddiy rejalashtirishni ko'rib chiqdik, lekin bu erda chegaralar mavjud edi. Uchta sabab tufayli - takroriy bajarilishi, nosozlikdan tiklanmasligi va dinamik rejalashtirishni boshqarishning imkoni yo'qligi sababli boshqa alternativalarni ko'rib chiqdik. Taqsimlangan rejalashtirishni amalga oshirish uchun topilgan texnologiya steklari ShedLock, db-scheduler, Spring Batch, Quartz Scheduler edi. ShedLock oddiy qulflanish asosida bo'lib, murakkab reja ifodalari qiyin edi. db-scheduler yengil, lekin trigger ifodalari cheklangan, Spring Batch esa partiyalarga yo'naltirilgan bo'lib, real vaqtda rejalashtirish uchun mos emas edi.

Oxir-oqibat, Quartz Scheduler ning JDBC Clustering rejimini tanlashimizning sabablari quyidagilardir.

  1. JDBC asosidagi klasterni boshqarish: Alohida ZooKeeper yoki Redis bo'lmagan holda, allaqachon foydalanilayotgan RDBMS orqali klaster tugunlari orasida boshqarish mumkin.

  2. Dinamik reja boshqaruvi: Ish vaqtida Jobni ro'yxatga olish/ozgartirish/o'chirish mumkin, REST API orqali reja boshqarish mumkin.

  3. Boyicha trigger turlari: CronTrigger, SimpleTrigger, CalendarIntervalTrigger kabi turli takroriy shablonlarni mahalliy ravishda qo'llab-quvvatlaydi.

  4. Misfire bilan ishlash: Tarmoq kechikishlari yoki tugun nosozligi tufayli o'tkazib yuborilgan vazifalar uchun siyosatni (tez amalga oshirish, e'tiborsiz qoldirish, qayta rejalashtirish va hokazo) batafsil boshqarishingiz mumkin.

  5. Spring Boot integratsiyasi: spring-boot-starter-quartz orqali avtomatik sozlash taqdim etilgan, boshlang'ich sozlash nisbatan oddiy qilib qo'yilganligi afzallik hisoblanadi.

Qo'shimcha infratuzilmasiz tarqatilgan muhitda xavfsiz rejalashtirish imkonini beradigan Quartz JDBC klasteridan foydalandik.

3. Loyihalash: Ilova va Quartz roli ajratilishi

Application va Quartzning mas'uliyatlarini aniq ajratgan tuzilmani qo'llaganligi muhimdir. Client jadval bilan bog'liq jadval ma'lumotlarini yaratib, yuborilganda, DBda saqlanadi va ro'yxatga olish domen voqeasini chiqaradi. Keyin domen voqeasini qabul qilib, ushbu jadval ma'lumotlari asosida Quartzga ro'yxatdan o'tgan Job va Triggerlarni yaratib, Schedulerga aks ettiradi. Quartzga ro'yxatdan o'tgan jadval Quartz Scheduler ichidagi harakatlar orqali qayta ishlanadi, App esa ishga tushirish konteksti (Context) boshqaruvi, Log yuklash kabi funksiyalarni bajaradi.

Xulosa qilib aytganda, Application jadvalni belgilash/logni boshqaradi va Quartz jadvalni bajarishni nazorat qiladi.

4. Amalga bo'lagi

4.1 Klaster sozlamalari

Quartz klasterlash (tarqatilgan rejalashtirish)ning asosiy jihati application.ymlBu sozlamalar. Bir nechta podda mavjud bo'lgan rejalashtirish xizmatlari bir xil DB ga qarab, DB vaqti orqali ishlarning ijrosini tarqatib boshqaradi.

spring:
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: never    # DB Schema를 별도로 관리하는 경우 (예시: Flyway)
    properties:
      org.quartz:
        scheduler:
          instanceName: SchedulerName
          instanceId: AUTO          # 노드마다 고유 ID 자동 생성
        jobStore:
          class: LocalDataSourceJobStore
          isClustered: true          # 클러스터 모드 활성화
	    tablePrefix: qrtz_        # 테이블 prefix 설정(vendor별 대소문자 특징 주의)
          clusterCheckinInterval: 10000  # 10초마다 하트비트
          misfireThreshold: 60000    # 60초 이내 지연은 허용
        threadPool:
          class: SimpleThreadPool
          threadCount: 10            # 노드당 동시 실행 10개
          threadPriority: 5

Har bir sozlama bo'yicha ma'no bilan tanishsak:

  • isClustered: Bu sozlama yoqilganda, Quartz DB ga QRTZ_LOCKSTarjimai orqali tarqatilgan qulflarni olish va, QRTZ_SCHEDULER_STATEJadvalga muntazam yurak urishini yozadi.

  • instanceId: AUTO: Har bir Pod ishga tushganda noyob instansiyalash ID avtomatik ravishda yaratiladi. K8s muhitida har bir Podga boshqa ID beriladi, shunda klaster ichidagi har bir tugunni aniqlash mumkin.

  • clusterCheckinInterval: 10000: Har bir 10 soniyada yuritishlarni DBga qayd etadi. Boshqa tugunlar ushbu yuritishni tekshirib, ma'lum bir tugunning tirikligi haqida qaror qiladi. Agar tugun ushbu intervallardan bir necha baravar ko'p vaqt davomida javob bermasa, ushbu tugun o'lgan deb hisoblanadi va ushbu tugun egallagan ish boshqa tugunlarga o'tkaziladi.

  • misfireThreshold: 60000: Belgilanadigan bajarish vaqtidan 60 soniya ichidagi kechikish normal hisoblanadi.

  • threadCount: 10: Bir tugunda bir vaqtda 10 ta Jobni amalga oshirish mumkin. Butun klasterning maksimal bir vaqtning o'zida ishlov berish quvvati (tugunlar soni × 10) ga asoslanadi.

Klassifikatsiya ishlashi uchun Quartz uchun maxsus jadval DBda mavjud bo'lishi kerak.

(ma'lumot - ddl) https://github.com/quartznet/quartznet/tree/main/database/tables

Umuman olganda 11 ta Quartz jadvali kerak, men Flyway migratsiya skriptlari yordamida xizmatni tarqatishda avtomatik ravishda yaratilishi uchun konfiguratsiya qildim.

4.2 Trigger strategiyasi

Quartz'ga ish (jadval) qo'shishda, sekunddan yilgacha bo'lgan turli takroriy jadval sozlamalari uchun oddiy Trigger bilan chegaralar mavjud. Cron ifodasi bunday naqshlarni aniq ifodalashda qiyinchilik tug'diradi. "Har 3 kunda" ni aniq ifodalash mumkin emas (har oy 1, 4, 7 ... kabi sanalar doimiy bo'lib qoladi) va "har 2 haftada" yoki "har 3 oyda" kabi naqshlarni cron bilan ifodalash imkoni yo'q. Shuningdek, DST (Yozgi vaqtga o'tish) o'zgarishida CronTrigger vaqtni shunchaki yozmada talqin qilib, kutilmagan xatti-harakatlarga sabab bo'lishi mumkin. Shuning uchun CalendarIntervalTrigger bilan birga ishlatilishi, kalendar asosida "N kun o'tgach", "N hafta o'tgach", "N oy o'tgach" ni aniq hisoblash imkonini beradi. Qo'shimcha ravishda Quartz Triggerda jadval bajarilishi muvaffaqiyatsiz bo'lganda Misfire siyosatini o'rnatish mumkin. Aksariyat hollarda, yo'qotilgan bajarilish bo'lsa, darhol 1 marta bajarib keyingi qoidalarga amal qilish siyosati qo'llanilgan.

4.3 Aqlli Qayta Jadval Kiritish

App va Quartz Scheduler o'rtasidagi jadval sinxronizatsiyasi muhimdir. App tomonidan boshqariladigan jadval ma'lumotlari o'zgarganida, bu o'zgarishlarni Quartz Scheduler'ga ham aks ettirish zarur. Triggerlarni qayta ro'yxatdan o'tkazish zarurati bormi yoki yo'qmi, buni aniq ajratish muhimdir. Agar jadval entitiyalari o'zgartirilsa, domen hodisasini chiqarish va shu orqali Quartz jadvalini qayta ro'yxatdan o'tkazish yoki yangilanish kerak bo'lgan holatlarni aniq ajratish zarur.

Agar har bir jadval o'zgartirish uchun qayta jadvalga olish amalga oshirilsa, tugatish → holat o'zgarishi → qayta jadvalga olish → bajarish → holat o'zgarishi → ... cheksiz halqaga tushib qoladi. O'zgartirish turini tasniflash va jadval bajarilishiga oid maydonlar o'zgarganmi yoki aloqasi bo'lmagan maydonlar o'zgarganmi, buni yaxshi aniqlash va shartlar yozish muhimdir.

5. Uchrashgan muammolar va yechimlar

5.1 Takroriy bajarishni oldini olish: Ikki marta himoya

Klaster muhitida sinov o'tkazayotganda, xuddi shu hodisaning 2 marta e'lon qilinishi sodir bo'ldi va hodisalar brokerining qayta jo'natilishi natijasida bir xil jadval takroran ro'yxatga olindi. Bir xil Job bir nechta tugunlarda bir vaqtning o'zida bajarilishini oldini olish uchun ikkita muhofaza mexanizmini qo'lladik. Birinchidan, Job klassiga @DisallowConcurrentExecution annotatsiyasini qo'llash.

Klaster DB loki bir xil Trigger'ning yuzaga kelishi vaqtida bir necha tugunlarning bir vaqtning o'zida qulflanishini oldini olish uchun "faqat bitta tugun ishga tushadi"ni ta'minlaydi va @DisallowConcurrentExecution, takrorlanuvchi jadvalda oldingi bajarilishi hali tugamagan bo'lsa, keyingi yuzaga kelishini boshlanishini oldini oladi, shuning uchun bir xil Job bir vaqtning o'zida 2 tadan ko'p bajarilmasligi kerak. Ikkinchidan, mantiqda Quartz'ga jadvalni ro'yxatga olishda takrorlanishini tekshirishni ta'minladik.

5.2 "Oxirgi bajarilish" aniqlash takvimi

Takvim tugash shartlariga yetganida ham davom etish muammosi tug‘ildi va cheklangan takvimda ortiqcha bajarilishlar sodir bo‘ldi. Turli takrorlash shartlari bilan birga, 5 marta bajarilgandan so‘ng tugash, belgilangan vaqtdan keyin bajarilish taqiqlangan kabi turli takrorlanish tugash shartlari mavjud bo‘lib, ko‘plab holatlarni hisobga olish kerak edi. Asosiy masala - ilova tomonidan boshqariladigan takvim ma’lumotlari va Quartz tomonidan avtomatik ravishda boshqariladigan takvim ma’lumotlarini to‘g‘ri solishtirishdir. Tugash aniqlash Quartz + Ilova darajasida ikki tomonlama tekshirishdir. Masalan, takrorlash takvimining keyingi bajarilishini tekshirishda, Quartz tomonidan avtomatik ravishda hisoblangan qiymat (Next Fire Time va hk.) orqali keyingi takrorlash takvimi mavjudligini aniqlash mumkin. Keyinchalik, takvimni belgilagandagi takrorlash tugash vaqti/takrorlash soni kabi narsalarni tekshirib, takrorlanish tugaganini yoki davom etayotganini aniqlash kerak.

5.3 TimeZone birlashtirish

Vaqt bilan ishlaganda har doim TimeZone sozlamalarini hisobga olish kerak. Turli vaziyatlarda vaqt zonasi boshqaruvi inobatga olinmasa, foydalanuvchilar va ishlab chiquvchilar uchun chalkashlik tug'dirishi mumkin. Shuning uchun, mantiqiy jarayonlar, DB saqlash, voqea payload, API so'rov/ javob kabi barcha sohalarda vaqt ma'lumotlarini UTCga asoslangan holda boshqardik va faqat frondan ko'rsatilganda dayjsni ishlatib, brauzer vaqt zonasida ko'rsatdik. Bu orqali birlashtirilgan vaqt boshqaruvi bilan ishlab chiquvchilar ham, foydalanuvchilar ham chalkashmaydi.

6. Xulosa

Quartz Scheduler ning JDBC Klasterlashni qo‘llash jarayonida eng ko‘zga tashlangan narsa bu avvalo DB asosidagi muvofiqlikning amaliyotidir. Ajratilgan tarqatish muxandisi bo'lmasa ham, RDBMSning qator darajadagi blokirovkasi orqali etarlicha ishonchli klaster rejalashtirishni amalga oshirish mumkin edi. Allaqachon ishlatib bo'lingan DBdan foydalanish infra tuzilishi murakkabligini oshirmaslikning eng katta afzalligi edi. Keyingisi esa hodisaga asoslangan arxitekturani uyg'unlashtirish edi. Domen voqealari orqali rejalarning hayot tsikli (ro'yxatga olish/tahrirlash/o'chirish) Quartz bilan bog'langanida, qiziqishlar ajratish tabiiy bo'ldi. Biroq, voqea ketma-ketligi natijasidagi salbiy ta'sirlarni (cheksiz tsikllar va boshqalar) oldindan taxmin qilish va himoya qilish muhim edi. Nihoyat, turli holatlarga javob berish masalasi bor. Foydalanish naqshlariga mos keladigan trigger turini ajratib olish va turli voqea tugash shartlariga qarab rejalashtirishni ko'rsatish, shuningdek, voqea ketma-ketligi natijasidagi salbiy ta'sirlarni oldindan taxmin qilish va himoya qilish kabi ko'p narsalarni hisobga olish kerak edi. Hozirda bu hujjatda aytib o'tilgan xizmat hali faol foydalanish jarayoniga o'rnashmagan. Keyinchalik funktsiyalar qo'shilishi va turli joylarda foydalanilishi bilan ko'proq muammolarni hal qilish jarayonida doimiy ravishda rivojlanishini kutmoqdaman.

Quartz 2001 yildan beri ishlab chiqilgan eski loyiha, biroq JDBC klasterlashtirish kabi asosiy mexanizmi hozirgi kungacha mustahkam va amaliy. Ayniqsa, allaqachon RDBMS dan foydalanayotgan mikroservis muhitida, qo'shimcha infratuzilmalar holda taqsimlangan rejalashtirishni amalga oshirish zarur bo'lsa, bu yetarlicha amaliy variant deb o'ylayman.

qo'llanma materiallar

Quartz Scheduler rasmiy veb-sayti

Spring Quartz rasmiy veb-sayti

Tistory, Velog blog-

Jigar

Site footer