Vizend obuna funksiyasini loyihalash va amalga oshirish

Vizend obuna funksiyasini loyihalash va amalga oshirish

O'zini tarqatadigan platformaning obuna dizayni

Vizend Kollex obunasida uchragan tarqatishning birligi muammosi va yechimi

1. Fon va muammo ta'rifi

Vizend Showcase Kollexni (bir nechta mikroservislarni birlashtirgan tarqatish birligi) Pavilion (tenant) asosida Kubernetes klasteriga GitOps usuli bilan taqdim etuvchi platformadir. Kollex foydalanuvchilarga ko'rsatiladigan ekran birligi bo'lgan Episode va ushbu Episode'ni qo'llab-quvvatlovchi platforma orqa xizmatlari (Drama, gate, metro, showcase va hokazo) dan iborat. Foydalanuvchi Kollexga obuna bo'lsa, Showcase obuna holatini SubscriptionLifecycleEvent sifatida Metro (authorization, obuna holatini boshqarish uchun platforma xizmati) ga e'lon qiladi va bir vaqtda GitOps omborida Kubernetes manifestini yozib, ArgoCD bilan sinxronlashtiradi. Ya'ni tarqatish orkestratsiyasi sub'yekti tashqi xizmat emas, balki Showcase o'zidir.

Ushbu pipeline obuna maqsadi tashqi ish yuklari bo'lganida bir tomonlama asinxron oqim sifatida toza tarzda tartibga solinadi. Muammo Vizend platformasini tashkil etuvchi Showcase xizmatining o'zining Kollex sifatida qadoqlanganligidir. Ya'ni Showcase o'zini obuna bo'lish uchun tarqatiladigan maqsad sifatida ko'rsatadi va bu holda obunani boshqaruvchi sub'yekt va obuna tufayli o'zgartiriladigan maqsad bir xil jarayon bo'ladi. Ushbu maqola ushbu o'z-o'zini havola qiluvchi tarqatish tuzilishida yuzaga keladigan birlik muammosi va buni holat mashinasi va hodisa bo'linish orqali qanday yechishga erishilganligini muhokama qiladi.

2. Builtin Kollex va o'z-o'zini havola qiluvchi tarqatish

2-1. Builtin Kollex ta'rifi

Kollex entitilari isBuiltin xususiyati yordamida platforma tashkil etish to'plami bormi yoki yo'qligini aniqlaydi. builtin=true bo'lgan Kollex Vizend platformasini tashkil etuvchi xizmatlar to'plamini anglatadi. Pavilion yangi yaratganda, Showcase bu Builtin Kollex obunasini avtomatik ravishda ishga tushiradi va natijada Showcase va uning bog'liq infra tuzilmalar ushbu Pavilion klasteriga tarqatiladi.

Oddiy Kollex obunasi va Builtin Kollex obunasini ajratib turuvchi muhim cheklovlar quyidagicha qisqacha bayon etiladi.

Tarqatish tugallanganligini aniqlaydi va tarqatish jarayonida o'zgartiriladigan jarayon bilan bir xil.

Showcase yangi versiyaga chiqarilgan paytda mavjud bo'lgan Showcase jarayoni tugatiladi. Tarqatish tugallanganini tasdiqlash mumkin bo'lgan sub'yekti – yangi jarayonning ishga tushishidan keyin faqat o'sha jarayon. Ushbu jarayon oldingi jarayonning xotira holatini mutlaqo baham ko'rmaydi. Shuning uchun Builtin obunasining hayot tsikli, bitta jarayonning uzluksiz bajarilishini faraz qiluvchi oddiy obunadan ajratilgan holat o'zgarish yo'liga ega bo'lishi kerak.

2-2. SubscribeLifecycleState o'zgarish yo'li

Ikkita obuna turi bitta SubscribeLifecycleState enumni baham ko'radi, ammo turli yo'llarni bosib o'tadi. Oddiy obuna bitta tranzaksiya oqimi bo'lib tugaydi, Builtin obuna esa jarayonni qayta ishga tushirish nuqtasi qirralari bo'yicha ajratilgan.

// 일반 Kollex 구독
Preparing → SyncingMetro → Completed | Failed
// Builtin Kollex 구독 (Self-Deploy)
Preparing
  → SyncingMetro          // 1차 구독 이벤트 발행 (Metro)
  → PreparingSelfDeploy   // self-deploy 이벤트 발행 직전 단계
  → DeployingSelf         // 자기 배포 진행 — 프로세스 종료 임박
  ──────── 프로세스 재기동 경계 ────────
  → SelfDeployVerified    // 신규 프로세스의 이미지 버전 검증 통과
  → FinalizingMetro       // 최종 배포 이벤트 발행 (Drama 포함)
  → Completed
// 실패 전이
DeployingSelf   → SelfDeployFailed   // 이미지 버전 불일치
FinalizingMetro → FinalizeFailed     // 이벤트 발행 실패

DeployingSelf holatida jarayon tugasa ham, SubscribeLifecycle yozuvlari ma'lumotlar bazasida davlatga ko'chiriladi. Qayta ishga tushirilgan jarayon ushbu yozuvni bitta haqiqiy manba sifatida olib, to'xtatilgan hayot tsiklini davom ettiradi. Holatni xotira qatlami emas, balki doimiy qatlamga qo'yish jarayon o'zgarishlarini ko'tarishda asosiy dizayndir.

2-3. Tarqatish hodisalarining 2 bosqichli bo'linishi

Builtin obuna SubscriptionLifecycleEvent'ni ikki bosqichga bo'lib yuboradi. Ushbu bo'linishning asosiy sababi Episode va Drama orasidagi tarqatish bog'liqlik tartibidir.

Showcase foydalanuvchilarga ko'rsatadigan Episode bo'lib, shuningdek, gate·metro·porto bilan birga tarqatiladigan platforma orqa qismi, ya'ni o'zini ko'rsatuvchi showcase Drama hisoblanadi. Builtin Kollex'ni obuna bo'lsangiz, bu showcase Drama obuna maqsadiga kiradi va natijada Showcase o'zini yangi versiyasi bilan almashtiradi. Agar showcase Drama hali yangi versiyaga birlashtirilmagan holda eski versiya jarayoni tugash signalini yuborsa, almashtirish tugamagan bo'lsa-da, obuna tugaganlik holati noto'g'ri ijobiy (false positive) yuzaga keladi.

Buni oldini olish uchun Metro'ga yuboriladigan birinchi obuna voqeasiga faqat Episode kiritiladi va showcase Drama chiqarilmaydi. showcase Drama'nin almashtirilishi alohida self-deploy voqeasi orqali Showcase ichida topshiriladi. Yangi jarayon normal ishga tushib, tasvir versiyasini tasdiqlaganidan keyin, Drama'ni o'z ichiga olgan oxirgi voqeani Metro'ga yuboradi. Ushbu oxirgi voqeani yuklash JSON formatida hayot tsikli yozuviga seriyalangan holda saqlanadi, qayta ishga tushirishdan keyin tiklansin.

// 1차 구독 이벤트 → Metro 발행. self-deploy 대상(showcase Drama)은 제외
eventProxy.produceEvent(subscriptionLifecycleEvent);   // Episode 포함, Drama 비움
// self-deploy 트리거 — Showcase 내부 이벤트. showcase Drama만 포함
SubscriptionLifecycleEvent selfDeployEvent = event.toBuilder()
    .episodes(List.of())
    .dramas(galleryDramas)
    .build();
applicationEventPublisher.publishEvent(selfDeployEvent);
// 최종 이벤트(Drama 포함)는 재기동 후 Metro 발행을 위해 직렬화 보관
lifecycle.setFinalEventPayloadJson(finalEvent.toJson());

▲ Oxirgi voqea yuklashni barqarorlashtirish sababi — qayta ishga tushirilgan jarayon oldingi jarayonning xotirada vaqtinchalik kontekstini qabul qila olmaydi

3. Qayta ishga tushirishdan keyin o'z-o'zini tarqatishni tekshirish

3-1. Ishga tushirish vaqti tekshiruvi — ApplicationReadyEvent

Yangi jarayon ishga tushishi tugagach, BuiltinSubscribeLifecycleCompletionTask ApplicationReadyEvent'ni qabul qilib, tekshirishni amalga oshiradi. DeployingSelf holatida to'xtatilgan hayot tsiklini ko'rib chiqqach, hayot tsikliga yozilgan kutgan tasvir versiyasi (expectedImageVersion) va hozirda ishlayotgan jarayonning kuzatilayotgan tasvir versiyasini (observedImageVersion) taqqoslaydi. Ikkala qiymat mos kelsa SelfDeployVerified holatiga o'tadi, agar mos kelmasa SelfDeployFailed deb yoziladi.

@EventListener(ApplicationReadyEvent.class)
@Transactional
public void verifySelfDeployOnStartup() {
    if (!enabled) return;
    String observed = resolveObservedImageVersion();
    List<SubscribeLifecycle> deploying =
        subscribeLifecycleLogic.findByStateIn(List.of(DeployingSelf));
    for (SubscribeLifecycle lifecycle : deploying) {
        if (Objects.equals(lifecycle.getExpectedImageVersion(), observed)) {
            lifecycle.moveTo(SelfDeployVerified);
        } else {
            lifecycle.recordFailure(SelfDeployFailed, "image version mismatch");
        }
        subscribeLifecycleLogic.modifySubscribeLifecycle(lifecycle);
    }
}

Versiya mosligini tekshirish orqali "obuna maqsad qilgan tasvir haqiqatan ham ishga tushdimi"ligini kafolatlaydi. Ro'lni kechiktirish tufayli eski versiya jarayoni vaqtincha yashayotgan yoki qaytarish orqali boshqa versiya ishga tushgan hollarda muvaffaqiyatsiz deb aniqlanishi mumkin.

3-2. Kuzatilgan versiyani talqin qilish va muhitdan mustaqillik

Kuzatilgan tasvir versiyasi bajarilish muhitiga qarab turlicha yo'llar orqali qo'shiladi. Kubernetes muhitida tasvir tegi atrof-muhit o'zgaruvchilari bilan uzatiladi va mahalliy rivojlantirish muhitida ushbu o'zgaruvchilar mavjud emas. resolveObservedImageVersion() ustuvorlikka ega bo'lgan nomzod xususiyatlarini ketma-ket qidirib, hammasi yo'q bo'lsa, paketlangan JAR manifestining Implementation-Version'iga qaytadi.

private String resolveObservedImageVersion() {
    List<String> candidates = List.of(
        "vizend.gallery.self-deploy.observed-image-version",
        "VIZEND_GALLERY_IMAGE_TAG",
        "GALLERY_IMAGE_TAG",
        "IMAGE_TAG",
        "BUILD_VERSION"
    );
    for (String name : candidates) {
        String value = environment.getProperty(name);
        if (StringUtils.hasText(value)) return value.trim();
    }
    Package pkg = getClass().getPackage();          // 로컬 폴백
    return pkg != null ? pkg.getImplementationVersion() : null;
}

Ushbu ustuvorlik zanjiri tufayli tekshirish lojikalari bajarilish muhitini shartli ravishda tahlil qilmaydi. Xuddi shu kod CI/CD quvurida Docker tasvir teglari bilan, mahalliy ravishda esa qurilish mahsulotlari versiyasi bilan ishlaydi va muhitga oid bo'linishlar kodga singib ketmaydi.

3-3. Oxirgi voqeani yuborish va idempotentlik

SelfDeployVerified holatidagi hayot tsikli xodimning qayd etish oralig'ida oxirgi voqeani yuboradi. Tekshirish va yuborishni ajratish sababi, ApplicationReadyEvent vaqtidagi bo'sh boshqarilamasligini tugatgan bo'lsada, xabarnoma brokeri va tranzaksiya menejeri to'liq barqarorlashishi uchun qisqa muddatli kutish talab etiladi. Sinxron yuborish o'rniga polling asosidagi asinxron yuborishni tanlab, ishga tushirilishi darhol natija tugavermasligini ta'minlaydi.

@Scheduled(fixedDelayString =
    "${vizend.gallery.builtin-subscribe.lifecycle.finalize-interval-ms:10000}")
@Transactional
public void publishFinalMetroEvents() {
    List<SubscribeLifecycle> verified =
        subscribeLifecycleLogic.findByStateIn(List.of(SelfDeployVerified));
    for (SubscribeLifecycle lifecycle : verified) {
        if (lifecycle.isFinalEventPublished()) continue;   // 멱등 가드
        lifecycle.moveTo(FinalizingMetro);
        SubscriptionLifecycleEvent event =
            SubscriptionLifecycleEvent.fromJson(lifecycle.getFinalEventPayloadJson());
        eventProxy.produceEvent(event);
        lifecycle.markFinalEventPublished(LocalDateTime.now());
    }
}

isFinalEventPublished() gvardiyasi idempotentlikni kafolatlaydi. Xodimning davrli qayta ishga tushirilishi, yuborishdan so'ng holat yangilanishining muvaffaqiyatsiz bo'lishi, birgalikdagi instansiyalar orasidagi raqobat kabi har qanday holda bitta hayot tsiklining oxirgi voqeasi takroran yuborilmaydi. Voqea iste'molchilar tomoni (Metro) at-least-once yetkazilishi kutilgan muhitda, yuboruvchi o'zini mavjudligini kamaytirish orqali umumiy quvurlar integratsiyasini soddalashtiradi.

4. Tashqi Drama — mavjud infratuzilma bilan to'qnashuvni oldini olish

Pavilion klasterida allaqachon ishlayotgan umumiy infratuzilma bo'lishi mumkin. Kollex'ning yangi obunasidagi Drama agar mavjud infratuzilma bilan takrorlansa, qayta o'rnatish ishlayotgan ish yuklarini chalkashtirib yuborish xavfini keltirib chiqaradi. Shunday qilib, tashqi boshqariladigan Drama'larni Tashqi Drama deb tasniflaymiz. Foydalanuvchilar obuna paytida ma'lum Drama'ni Tashqi deb belgilab, ushbu identifikator SubscribeCommand.externalTargetIds orqali uzatiladi.

Bu yerda asosiy dizayn qarori "tarqatmaslik" va "voqeani chiqarib tashlash"ni ajratishdir. Tashqi Drama GitOps manifest yaratish ob'ekti dari chiqarilmasligi kerak, lekin obuna voqea yuklamalaridan chiqarib tashlanmasligi kerak. Metro ruxsat va xizmat kashfiyot uchun Drama'ning rol xaritalashini — xizmat nomi, port, rol identifikatori — talab qiladi va bu meta ma'lumotlar manifest yaratish masalasidan qat'i nazar uzatilishi kerak.

// External Drama: 이벤트에는 포함, 매니페스트 생성만 skip 지시
List<SubscriptionTargetSpec> dramaTargets = dramas.stream()
    .map(drama -> {
        boolean external = command.getExternalTargetIds().contains(drama.getId());
        return SubscriptionTargetSpec.of(drama).withExternal(external);
    })
    .toList();
// Showcase 배포 단계는 isExternal 플래그로 매니페스트 생성 여부를 분기

Dastlabki ijro Tashqi Drama'ni voqea ro'yxatidan to'liq chiqarib tashladi va natijada Metro ushbu Drama bilan bog'langan nuqtalarni talqin qila olmadi va xizmatlar orasidagi muloqot muvaffaqiyatsiz bo'ldi. "Tarqatilmaydi" qarori "yo'q" deb xato tarqatilgan holat bo'lib, tarqatish siyosati va topologiya axborotlarini bir xil kanal orqali ajratib ko'rsatish lozimligini o'rgatdi.

5. GID — ko'p ijarachilar identifikator tizimi

Vizendda bir nechta Pavilion bir xil Kollex'ni mustaqil ravishda obuna qiladi. Har bir Pavilion alohida klasterga ega bo'lib, Kollex'u tashkil etuvchi Episode·Drama har bir Pavilion uchun alohida mahalliy identifikator bilan ro'yxatdan o'tadi. Bu struktura mahalliy identifikatorlar orqali bir-biridan farqlanmaydigan ijaralarning resurslarini global ravishda ajratishga imkon bermaydi va identifikator to'qnashuvlariga olib keladi.

GID (Global ID) klaster konteksti identifikatorini (CID) va Pavilion mahalliy identifikatorini birlashtirib global yagona identifikatsiyani ta'minlaydi.

// 로컬 식별자 — Pavilion 내에서만 유일 (NT:1 = Pavilion ID)
KOLLEX_ID   = "NT:1-K0001"
EPISODE_ID  = "NT:1-E0002"
DRAMA_ID    = "NT:1-D000b"
// GID — 플랫폼 전역에서 유일 (X8FD = CID, 클러스터 컨텍스트)
KOLLEX_GID  = "X8FD-NT:1-K0001"
EPISODE_GID = "X8FD-NT:1-E0002"
DRAMA_GID   = "X8FD-NT:1-D000b"

SubscriptionLifecycleEvent bilan GID uzatiladi, shuning uchun Metro va Showcase'ning tarqatish bosqichlari voqea qaysi klaster·ijarachining qaysi resurslariga ishora qilishini aniq belgilash imkonini beradi. Dizayn qo'llanmasi esa EpisodeKey·DramaKey·KollexKey to'liq GID'siga ishonadi va GID bo'sh bo'lsa, hech qanday fallbacksiz muvaffaqiyatsizlikka uchrash kerakligini belgilaydi. Obuna mantiqining E2E testi voqea yuklamalaridagi GID tarkibini aniq tekshirishni ham talab qiladi, chunki bu identifikatsiya tizimi ko'p ijarachilik muborakligi uchun asosiy shartdir.

// E2E 검증 — GID가 CID + 로컬 식별자 조합으로 구성되는지 확인
assertThat(event.getEpisodes().get(0)
    .getEpisodeInfo().getEpisodeKey().getGid())
    .isEqualTo(EPISODE_GID);   // "X8FD-NT:1-E0002"
assertThat(event.getEpisodes().get(0)
    .getEpisodeInfo().getEpisodeKey().getId())
    .isEqualTo(EPISODE_ID);    // "NT:1-E0002"

6. Tushunish

Kollex obuna funksiyasini amalga oshirish murakkabligi "obuna" degan soha terminining soddaligiga qarama-qarshi edi. O'z-o'zini isbotlaydigan tarqatish deb ataladigan cheklov bitta alohida kompleks tizimning konsistensiya muammolarini keltirib chiqardi.

  • Holatni saqlash — tarqatish bilan almashtirilayotgan jarayonning rivojlanish holatini ma'lumotlar bazasida saqlab, qayta ishga tushirishni tutadi.

  • Voqeani 2 bosqichga bo'lish — infratuzilma qaramligi yig'ilishidan oldin noto'g'ri tugashni to'xtatadi.

  • Versiya tasdiqlash — maqsadga muvofiq rasmiy suratning haqiqatan ham ishga tushirilayotganini muhitdan mustaqil ravishda ta'minlaydi.

  • Idempotent chiqarish — at-least-once etkazish muhitida chiqaruvchi tomondan takrorlashni cheklaydi.

  • Global identifikator — ko'p ijarachilar mahalliy identifikator to'qnashuvini GID orqali hal etadi.

Ushbu muammoning barchasi bitta ham bo'lmasa, obuna tranzaksiyasi muvaffaqiyatli yakunlanadi, lekin haqiqiy xizmat ishlamay diqqatga sazovor bo'lgan qism muvaffaqiyatsizligiga olib keladi. Mintaqa dasturiy ko'rinishi ostida joylashgan tarqatma konsistensiya muammolarini, saqlash holati mashinasi va voqealarni bo'lishadigan tizim yordamida hal qilish imkoniyatini bu funktsiya o'z ichiga olgan asosiy dizayn tajribasi hisoblanadi.

Jeyms

Site footer