Tarqalgan tizimning kompilyatsiya qilinadigan konfiguratsiyasi

Ushbu postda biz taqsimlangan tizim konfiguratsiyasi bilan shug'ullanishning qiziqarli usulini baham ko'rmoqchimiz.
Konfiguratsiya to'g'ridan-to'g'ri Scala tilida xavfsiz tarzda taqdim etiladi. Amalga oshirish misoli batafsil tavsiflangan. Taklifning turli jihatlari, jumladan, umumiy rivojlanish jarayoniga ta'siri muhokama qilinadi.

Tarqalgan tizimning kompilyatsiya qilinadigan konfiguratsiyasi

(rus tilida)

Kirish

Kuchli taqsimlangan tizimlarni qurish barcha tugunlarda to'g'ri va izchil konfiguratsiyadan foydalanishni talab qiladi. Oddiy yechim - matnli joylashtirish tavsifi (terraform, ansible yoki shunga o'xshash narsa) va avtomatik ravishda yaratilgan konfiguratsiya fayllaridan (ko'pincha - har bir tugun/rol uchun ajratilgan) foydalanish. Shuningdek, biz har bir aloqa tugunlarida bir xil versiyalarning bir xil protokollaridan foydalanishni xohlaymiz (aks holda biz mos kelmaslik muammolariga duch kelamiz). JVM dunyosida bu hech bo'lmaganda xabarlar kutubxonasi barcha aloqa tugunlarida bir xil versiyada bo'lishi kerakligini anglatadi.

Tizimni sinab ko'rish haqida nima deyish mumkin? Albatta, biz integratsiya testlariga kirishdan oldin barcha komponentlar uchun birlik testlarini o'tkazishimiz kerak. Ish vaqti bo'yicha sinov natijalarini ekstrapolyatsiya qilish uchun biz barcha kutubxonalarning versiyalari ish vaqtida ham, sinov muhitida ham bir xil saqlanishiga ishonch hosil qilishimiz kerak.

Integratsiya testlarini o'tkazishda, odatda, barcha tugunlarda bir xil sinf yo'liga ega bo'lish ancha oson bo'ladi. Biz faqat bir xil sinf yo'lini joylashtirishda ishlatilishiga ishonch hosil qilishimiz kerak. (Turli tugunlarda turli sinf yoʻllarini qoʻllash mumkin, lekin bu konfiguratsiyani koʻrsatish va uni toʻgʻri joylashtirish qiyinroq.) Shunday qilib, ishlarni soddalashtirish uchun biz barcha tugunlarda faqat bir xil sinf yoʻllarini koʻrib chiqamiz.

Konfiguratsiya dasturiy ta'minot bilan birga rivojlanish tendentsiyasiga ega. Biz odatda turli xil versiyalarni aniqlash uchun foydalanamiz
dasturiy ta'minot evolyutsiyasi bosqichlari. Versiya boshqaruvi ostidagi konfiguratsiyani qamrab olish va ba'zi teglar bilan turli konfiguratsiyalarni aniqlash oqilona ko'rinadi. Agar ishlab chiqarishda faqat bitta konfiguratsiya mavjud bo'lsa, biz identifikator sifatida bitta versiyadan foydalanishimiz mumkin. Ba'zan bizda bir nechta ishlab chiqarish muhiti bo'lishi mumkin. Va har bir muhit uchun bizga alohida konfiguratsiya bo'limi kerak bo'lishi mumkin. Shunday qilib, turli xil konfiguratsiyalarni yagona aniqlash uchun konfiguratsiyalar filial va versiya bilan belgilanishi mumkin. Har bir filial yorlig'i va versiyasi har bir tugundagi taqsimlangan tugunlar, portlar, tashqi resurslar, sinf yo'llari kutubxonasi versiyalarining yagona kombinatsiyasiga mos keladi. Bu yerda biz faqat bitta filialni qamrab olamiz va konfiguratsiyalarni boshqa artefaktlar kabi uch komponentli oʻnli versiya (1.2.3) orqali aniqlaymiz.

Zamonaviy sharoitlarda konfiguratsiya fayllari endi qo'lda o'zgartirilmaydi. Odatda biz yaratamiz
o'rnatish vaqtida konfiguratsiya fayllari va ularga hech qachon tegmang keyin. Shunday qilib, nima uchun biz konfiguratsiya fayllari uchun matn formatidan foydalanamiz deb so'rash mumkin? Konfiguratsiyani kompilyatsiya birligi ichiga joylashtirish va kompilyatsiya vaqtida konfiguratsiyani tekshirishdan foydalanish mumkin bo'lgan variant.

Ushbu postda biz tuzilgan artefaktda konfiguratsiyani saqlash g'oyasini ko'rib chiqamiz.

Kompilyatsiya qilinadigan konfiguratsiya

Ushbu bo'limda biz statik konfiguratsiya misolini ko'rib chiqamiz. Ikkita oddiy xizmat - echo xizmati va echo xizmati mijozi sozlanmoqda va amalga oshirilmoqda. Keyin ikkala xizmatga ega bo'lgan ikki xil taqsimlangan tizim yaratiladi. Ulardan biri bitta tugun konfiguratsiyasi uchun, ikkinchisi esa ikkita tugun konfiguratsiyasi uchun.

Oddiy taqsimlangan tizim bir nechta tugunlardan iborat. Tugunlarni quyidagi turlardan foydalanib aniqlash mumkin:

sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId

yoki faqat

case class NodeId(hostName: String)

yoki hatto

object Singleton
type NodeId = Singleton.type

Ushbu tugunlar turli xil rollarni bajaradi, ba'zi xizmatlarni ishga tushiradi va TCP/HTTP ulanishlari orqali boshqa tugunlar bilan aloqa o'rnatishi kerak.

TCP ulanishi uchun kamida port raqami talab qilinadi. Shuningdek, mijoz va server bir xil protokol bilan gaplashayotganiga ishonch hosil qilishni xohlaymiz. Tugunlar orasidagi ulanishni modellashtirish uchun quyidagi sinfni e'lon qilamiz:

case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])

qayerda Port faqat bir Int ruxsat etilgan diapazonda:

type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]

Tozalangan turlar

qarang Qayta ishlangan kutubxona. Muxtasar qilib aytganda, bu boshqa turlarga kompilyatsiya vaqti cheklovlarini qo'shish imkonini beradi. Ushbu holatda Int faqat port raqamini ko'rsatadigan 16 bitli qiymatlarga ega bo'lishga ruxsat beriladi. Ushbu konfiguratsiya yondashuvi uchun ushbu kutubxonadan foydalanish talabi yo'q. Bu shunchaki juda mos keladiganga o'xshaydi.

HTTP (REST) ​​uchun bizga xizmat yo'li ham kerak bo'lishi mumkin:

type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)

Fantom turi

Kompilyatsiya paytida protokolni aniqlash uchun biz argument turini e'lon qilishning Scala xususiyatidan foydalanamiz. Protocol bu sinfda ishlatilmaydi. Bu shunday deyiladi fantom turi. Ishlash vaqtida bizga kamdan-kam hollarda protokol identifikatorining namunasi kerak bo'ladi, shuning uchun biz uni saqlamaymiz. Kompilyatsiya paytida bu fantom turi qo'shimcha turdagi xavfsizlikni ta'minlaydi. Biz portni noto'g'ri protokol bilan o'tkaza olmaymiz.

Eng keng tarqalgan protokollardan biri bu Json seriyali REST API:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

qayerda RequestMessage mijoz serverga yuborishi mumkin bo'lgan xabarlarning asosiy turi va ResponseMessage serverdan kelgan javob xabaridir. Albatta, biz aloqa protokolini kerakli aniqlik bilan belgilaydigan boshqa protokol tavsiflarini yaratishimiz mumkin.

Ushbu postning maqsadlari uchun biz protokolning soddaroq versiyasidan foydalanamiz:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Ushbu protokolda so'rov xabari url-ga qo'shiladi va javob xabari oddiy qator sifatida qaytariladi.

Xizmat konfiguratsiyasi xizmat nomi, portlar to'plami va ba'zi bog'liqliklar bilan tavsiflanishi mumkin. Scala-da ushbu elementlarning barchasini qanday ko'rsatishning bir necha mumkin bo'lgan usullari mavjud (masalan, HList, algebraik ma'lumotlar turlari). Ushbu postning maqsadlari uchun biz tort naqshidan foydalanamiz va xususiyatlar sifatida birlashtiriladigan qismlarni (modullarni) ifodalaymiz. (Cake Pattern bu kompilyatsiya qilinadigan konfiguratsiya yondashuvi uchun shart emas. Bu g'oyani amalga oshirishning mumkin bo'lgan usullaridan biri.)

Bog'liqliklar boshqa tugunlarning so'nggi nuqtalari sifatida Cake Pattern yordamida ifodalanishi mumkin:

  type EchoProtocol[A] = SimpleHttpGetRest[A, A]

  trait EchoConfig[A] extends ServiceConfig {
    def portNumber: PortNumber = 8081
    def echoPort: PortWithPrefix[EchoProtocol[A]] = PortWithPrefix[EchoProtocol[A]](portNumber, "echo")
    def echoService: HttpSimpleGetEndPoint[NodeId, EchoProtocol[A]] = providedSimpleService(echoPort)
  }

Echo xizmatiga faqat port sozlanishi kerak. Va biz ushbu port echo protokolini qo'llab-quvvatlashini e'lon qilamiz. E'tibor bering, biz hozirda ma'lum bir portni ko'rsatishimiz shart emas, chunki xususiyatlar mavhum usullar deklaratsiyasiga ruxsat beradi. Agar biz mavhum usullardan foydalansak, kompilyator konfiguratsiya misolida amalga oshirishni talab qiladi. Bu erda biz amalga oshirishni taqdim etdik (8081) va agar biz uni aniq konfiguratsiyada o'tkazib yuborsak, u standart qiymat sifatida ishlatiladi.

Biz echo xizmati mijozi konfiguratsiyasida qaramlikni e'lon qilishimiz mumkin:

  trait EchoClientConfig[A] {
    def testMessage: String = "test"
    def pollInterval: FiniteDuration
    def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
  }

Tobelik bilan bir xil turga ega echoService. Xususan, xuddi shu protokolni talab qiladi. Shunday qilib, agar biz ushbu ikkita bog'liqlikni bog'lasak, ular to'g'ri ishlashiga amin bo'lishimiz mumkin.

Xizmatlarni amalga oshirish

Xizmatni ishga tushirish va qulay tarzda o'chirish uchun funksiya kerak. (Xizmatni o'chirish qobiliyati sinov uchun juda muhimdir.) Yana ma'lum bir konfiguratsiya uchun bunday funktsiyani belgilashning bir nechta variantlari mavjud (masalan, biz turdagi sinflardan foydalanishimiz mumkin). Ushbu post uchun biz yana kek naqshidan foydalanamiz. Biz xizmatni ko'rsatishimiz mumkin cats.Resource Bu allaqachon qavs va resursni chiqarishni ta'minlaydi. Resursni olish uchun biz konfiguratsiya va ba'zi ish vaqti kontekstini taqdim etishimiz kerak. Shunday qilib, xizmatni ishga tushirish funktsiyasi quyidagicha ko'rinishi mumkin:

  type ResourceReader[F[_], Config, A] = Reader[Config, Resource[F, A]]

  trait ServiceImpl[F[_]] {
    type Config
    def resource(
      implicit
      resolver: AddressResolver[F],
      timer: Timer[F],
      contextShift: ContextShift[F],
      ec: ExecutionContext,
      applicative: Applicative[F]
    ): ResourceReader[F, Config, Unit]
  }

qayerda

  • Config — ushbu xizmat starter tomonidan talab qilinadigan konfiguratsiya turi
  • AddressResolver — boshqa tugunlarning haqiqiy manzillarini olish imkoniyatiga ega boʻlgan ish vaqti obyekti (batafsil maʼlumot uchun oʻqishni davom eting).

boshqa turlari kelib chiqadi cats:

  • F[_] — effekt turi (eng oddiy holatda F[A] adolatli bo'lishi mumkin () => A. Ushbu postda biz foydalanamiz cats.IO.)
  • Reader[A,B] — funksiyaning ozmi-koʻpmi sinonimi A => B
  • cats.Resource — olish va chiqarish usullari mavjud
  • Timer — uxlash/vaqtni o‘lchash imkonini beradi
  • ContextShift - analogi ExecutionContext
  • Applicative — amaldagi funksiyalarning oʻrami (deyarli monad) (oxir-oqibat uni boshqa narsa bilan almashtirishimiz mumkin)

Ushbu interfeysdan foydalanib, biz bir nechta xizmatlarni amalga oshirishimiz mumkin. Masalan, hech narsa qilmaydigan xizmat:

  trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
    type Config <: Any
    def resource(...): ResourceReader[F, Config, Unit] =
      Reader(_ => Resource.pure[F, Unit](()))
  }

(Qarang Manba kodi boshqa xizmatlarni amalga oshirish uchun - echo xizmati,
echo mijozi va umr bo'yi boshqaruvchilar.)

Tugun bir nechta xizmatlarni ishga tushiradigan yagona ob'ektdir (resurslar zanjirini boshlash Cake Pattern tomonidan yoqiladi):

object SingleNodeImpl extends ZeroServiceImpl[IO]
  with EchoServiceService
  with EchoClientService
  with FiniteDurationLifecycleServiceImpl
{
  type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}

E'tibor bering, tugunda biz ushbu tugun uchun zarur bo'lgan konfiguratsiyaning aniq turini belgilaymiz. Kompilyator ob'ektni (Cake) turi etarli bo'lmagan holda qurishimizga ruxsat bermaydi, chunki har bir xizmat xususiyati cheklovni e'lon qiladi. Config turi. Shuningdek, biz to'liq konfiguratsiyani ta'minlamasdan tugunni ishga tushira olmaymiz.

Tugun manzili ruxsati

Ulanishni o'rnatish uchun bizga har bir tugun uchun haqiqiy xost manzili kerak. Bu konfiguratsiyaning boshqa qismlariga qaraganda keyinroq ma'lum bo'lishi mumkin. Shunday qilib, bizga tugun identifikatori va uning haqiqiy manzili o'rtasidagi xaritani taqdim qilish usuli kerak. Ushbu xaritalash funksiya:

case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
  def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}

Bunday funktsiyani amalga oshirishning bir necha mumkin bo'lgan usullari mavjud.

  1. Joylashtirishdan oldin, tugun xostlarini yaratish vaqtida biz haqiqiy manzillarni bilsak, biz haqiqiy manzillar bilan Scala kodini yaratishimiz va keyin qurishni ishga tushirishimiz mumkin (bu kompilyatsiya vaqtini tekshiradi va keyin integratsiya test to'plamini ishga tushiradi). Bu holda bizning xaritalash funksiyamiz statik jihatdan ma'lum va uni a kabi soddalashtirish mumkin Map[NodeId, NodeAddress].
  2. Ba'zan biz tugunni ishga tushirgandan keyingina haqiqiy manzillarni olamiz yoki bizda hali ishga tushirilmagan tugunlarning manzillari yo'q. Bunday holda, bizda barcha boshqa tugunlardan oldin ishga tushiriladigan kashfiyot xizmati bo'lishi mumkin va har bir tugun ushbu xizmatda o'z manzilini e'lon qilishi va bog'liqliklarga obuna bo'lishi mumkin.
  3. Agar o'zgartirishimiz mumkin bo'lsa /etc/hosts, biz oldindan belgilangan xost nomlaridan foydalanishimiz mumkin (masalan my-project-main-node va echo-backend) va bu nomni o'rnatish vaqtida IP manzili bilan bog'lang.

Ushbu postda biz ushbu holatlarni batafsilroq ko'rib chiqmaymiz. Aslida bizning o'yinchoq misolimizda barcha tugunlar bir xil IP manzilga ega bo'ladi - 127.0.0.1.

Ushbu postda biz ikkita taqsimlangan tizim sxemalarini ko'rib chiqamiz:

  1. Barcha xizmatlar bitta tugunga joylashtirilgan yagona tugun tartibi.
  2. Xizmat va mijoz turli tugunlarda joylashgan ikkita tugun tartibi.

a uchun konfiguratsiya yagona tugun tartibi quyidagicha:

Yagona tugun konfiguratsiyasi

object SingleNodeConfig extends EchoConfig[String] 
  with EchoClientConfig[String] with FiniteDurationLifecycleConfig
{
  case object Singleton // identifier of the single node 
  // configuration of server
  type NodeId = Singleton.type
  def nodeId = Singleton

  /** Type safe service port specification. */
  override def portNumber: PortNumber = 8088

  // configuration of client

  /** We'll use the service provided by the same host. */
  def echoServiceDependency = echoService

  override def testMessage: UrlPathElement = "hello"

  def pollInterval: FiniteDuration = 1.second

  // lifecycle controller configuration
  def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 requests, not 9.
}

Bu erda biz server va mijoz konfiguratsiyasini kengaytiradigan yagona konfiguratsiya yaratamiz. Bundan tashqari, biz mijoz va serverni odatdagidan keyin tugatadigan hayot aylanishi boshqaruvchisini sozlaymiz lifetime interval o'tadi.

Xuddi shu xizmatni amalga oshirish va konfiguratsiyalar to'plamidan ikkita alohida tugunli tizim tartibini yaratish uchun foydalanish mumkin. Biz faqat yaratishimiz kerak ikkita alohida tugun konfiguratsiyasi tegishli xizmatlar bilan:

Ikki tugun konfiguratsiyasi

  object NodeServerConfig extends EchoConfig[String] with SigTermLifecycleConfig
  {
    type NodeId = NodeIdImpl

    def nodeId = NodeServer

    override def portNumber: PortNumber = 8080
  }

  object NodeClientConfig extends EchoClientConfig[String] with FiniteDurationLifecycleConfig
  {
    // NB! dependency specification
    def echoServiceDependency = NodeServerConfig.echoService

    def pollInterval: FiniteDuration = 1.second

    def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 request, not 9.

    def testMessage: String = "dolly"
  }

Qarama-qarshilikni qanday aniqlaganimizni ko'ring. Biz joriy tugunga bog'liqlik sifatida boshqa tugunning taqdim etilgan xizmatini eslatib o'tamiz. Bog'liqlik turi tekshiriladi, chunki u protokolni tavsiflovchi fantom turini o'z ichiga oladi. Va ish vaqtida biz to'g'ri tugun identifikatoriga ega bo'lamiz. Bu taklif qilingan konfiguratsiya yondashuvining muhim jihatlaridan biridir. Bu bizga portni faqat bir marta o'rnatish va biz to'g'ri portga murojaat qilishimizga ishonch hosil qilish imkoniyatini beradi.

Ikki tugunni amalga oshirish

Ushbu konfiguratsiya uchun biz aynan bir xil xizmatlardan foydalanamiz. Hech qanday o'zgarish yo'q. Biroq, biz turli xil xizmatlar to'plamini o'z ichiga olgan ikkita turli tugun dasturlarini yaratamiz:

  object TwoJvmNodeServerImpl extends ZeroServiceImpl[IO] with EchoServiceService with SigIntLifecycleServiceImpl {
    type Config = EchoConfig[String] with SigTermLifecycleConfig
  }

  object TwoJvmNodeClientImpl extends ZeroServiceImpl[IO] with EchoClientService with FiniteDurationLifecycleServiceImpl {
    type Config = EchoClientConfig[String] with FiniteDurationLifecycleConfig
  }

Birinchi tugun serverni amalga oshiradi va u faqat server tomoni konfiguratsiyasini talab qiladi. Ikkinchi tugun mijozni amalga oshiradi va konfiguratsiyaning boshqa qismiga muhtoj. Ikkala tugun ham umr bo'yi spetsifikatsiyani talab qiladi. Ushbu post-xizmat tugunining maqsadlari uchun foydalanish tugatilishi mumkin bo'lgan cheksiz xizmat muddati bo'ladi SIGTERM, echo mijozi esa sozlangan cheklangan muddatdan keyin tugaydi. ga qarang starter ilovasi Batafsil ma'lumot olish uchun.

Umumiy rivojlanish jarayoni

Keling, ushbu yondashuv konfiguratsiya bilan ishlash uslubimizni qanday o'zgartirishini ko'rib chiqaylik.

Kod sifatida konfiguratsiya kompilyatsiya qilinadi va artefakt hosil qiladi. Konfiguratsiya artefaktini boshqa kod artefaktlaridan ajratish oqilona ko'rinadi. Ko'pincha biz bir xil kod bazasida ko'plab konfiguratsiyalarga ega bo'lishimiz mumkin. Va, albatta, bizda turli xil konfiguratsiya tarmoqlarining bir nechta versiyalari bo'lishi mumkin. Konfiguratsiyada biz kutubxonalarning alohida versiyalarini tanlashimiz mumkin va biz ushbu konfiguratsiyani o'rnatganimizda bu doimiy bo'lib qoladi.

Konfiguratsiya o'zgarishi kod o'zgarishiga aylanadi. Shunday qilib, u bir xil sifat kafolati jarayoni bilan qoplanishi kerak:

Chipta -> PR -> ko'rib chiqish -> birlashtirish -> uzluksiz integratsiya -> uzluksiz joylashtirish

Yondashuvning quyidagi oqibatlari mavjud:

  1. Konfiguratsiya muayyan tizim misoli uchun mos keladi. Tugunlar o'rtasida noto'g'ri ulanishning hech qanday usuli yo'qdek tuyuladi.
  2. Bitta tugunda konfiguratsiyani o'zgartirish oson emas. Tizimga kirish va ba'zi matnli fayllarni o'zgartirish mantiqsiz ko'rinadi. Shunday qilib, konfiguratsiya o'zgarishi kamroq mumkin bo'ladi.
  3. Kichik konfiguratsiya o'zgarishlarini qilish oson emas.
  4. Konfiguratsiya o'zgarishlarining ko'pchiligi bir xil ishlab chiqish jarayoniga amal qiladi va u biroz ko'rib chiqiladi.

Ishlab chiqarish konfiguratsiyasi uchun bizga alohida ombor kerakmi? Ishlab chiqarish konfiguratsiyasida biz ko'p odamlar qo'li etmaydigan qilib qo'ymoqchi bo'lgan nozik ma'lumotlar bo'lishi mumkin. Shunday qilib, ishlab chiqarish konfiguratsiyasini o'z ichiga olgan cheklangan kirishga ega alohida omborni saqlashga arziydi. Biz konfiguratsiyani ikki qismga bo'lishimiz mumkin - biri ishlab chiqarishning eng ochiq parametrlarini o'z ichiga oladi va ikkinchisi konfiguratsiyaning maxfiy qismini o'z ichiga oladi. Bu haqiqatan ham nozik narsalarga kirishni cheklab, ko'pchilik ishlab chiquvchilarga parametrlarning katta qismiga kirish imkonini beradi. Buni standart parametr qiymatlari bilan oraliq belgilar yordamida amalga oshirish oson.

Variations

Keling, boshqa konfiguratsiyalarni boshqarish usullari bilan solishtirganda taklif qilingan yondashuvning ijobiy va salbiy tomonlarini ko'rib chiqaylik.

Avvalo, biz konfiguratsiya bilan shug'ullanishning tavsiya etilgan usullarining turli jihatlariga bir nechta muqobillarni sanab o'tamiz:

  1. Maqsadli mashinadagi matn fayli.
  2. Markazlashtirilgan kalit-qiymat saqlash (masalan etcd/zookeeper).
  3. Jarayonni qayta ishga tushirmasdan qayta sozlanishi/qayta ishga tushirilishi mumkin bo'lgan quyi jarayon komponentlari.
  4. Artefakt va versiya boshqaruvidan tashqari konfiguratsiya.

Matn fayli vaqtinchalik tuzatishlar nuqtai nazaridan biroz moslashuvchanlikni beradi. Tizim ma'muri maqsadli tugunga kirishi, o'zgartirish kiritishi va xizmatni shunchaki qayta ishga tushirishi mumkin. Bu kattaroq tizimlar uchun unchalik yaxshi bo'lmasligi mumkin. O'zgarish ortida hech qanday iz qolmaydi. O'zgarish boshqa bir juft ko'z tomonidan ko'rib chiqilmaydi. O'zgarishlarga nima sabab bo'lganini aniqlash qiyin bo'lishi mumkin. Bu sinovdan o'tkazilmagan. Tarqalgan tizim nuqtai nazaridan ma'mur boshqa tugunlardan biridagi konfiguratsiyani yangilashni unutishi mumkin.

(Btw, agar oxir-oqibat matn konfiguratsiya fayllaridan foydalanishni boshlash zarurati tug'ilsa, biz faqat bir xil ishlab chiqarishi mumkin bo'lgan tahlilchi + validatorni qo'shishimiz kerak bo'ladi. Config yozing va bu matn konfiguratsiyasidan foydalanishni boshlash uchun etarli bo'ladi. Bu shuningdek, kompilyatsiya vaqti konfiguratsiyasining murakkabligi matnga asoslangan konfiguratsiyalarning murakkabligidan biroz kichikroq ekanligini ko'rsatadi, chunki matnga asoslangan versiyada bizga qo'shimcha kod kerak bo'ladi.)

Markazlashtirilgan kalit-qiymatni saqlash dastur meta parametrlarini tarqatish uchun yaxshi mexanizmdir. Bu erda biz konfiguratsiya qiymatlari va shunchaki ma'lumotlar nima deb hisoblashimiz haqida o'ylashimiz kerak. Funktsiya berilgan C => A => B biz odatda kamdan-kam o'zgaruvchan qiymatlarni chaqiramiz C "konfiguratsiya", tez-tez o'zgarib turadigan ma'lumotlar A - faqat ma'lumotlarni kiritish. Konfiguratsiya funktsiyaga ma'lumotlardan oldinroq taqdim etilishi kerak A. Ushbu fikrni hisobga olgan holda, biz konfiguratsiya ma'lumotlarini oddiy ma'lumotlardan ajratish uchun ishlatilishi mumkin bo'lgan o'zgarishlarning kutilgan chastotasi deb aytishimiz mumkin. Bundan tashqari, ma'lumotlar odatda bitta manbadan (foydalanuvchi) va konfiguratsiya boshqa manbadan (admin) keladi. Boshlash jarayonidan so'ng o'zgartirilishi mumkin bo'lgan parametrlar bilan ishlash dastur murakkabligining oshishiga olib keladi. Bunday parametrlar uchun biz ularni etkazib berish mexanizmi, tahlil qilish va tekshirish, noto'g'ri qiymatlarni qayta ishlashimiz kerak. Shunday qilib, dasturning murakkabligini kamaytirish uchun biz ish vaqtida o'zgarishi mumkin bo'lgan parametrlar sonini qisqartirganimiz ma'qul (yoki hatto ularni butunlay yo'q qilgan).

Ushbu post nuqtai nazaridan biz statik va dinamik parametrlarni farqlashimiz kerak. Agar xizmat mantig'i ish vaqtida ba'zi parametrlarning kamdan-kam o'zgarishini talab qilsa, biz ularni dinamik parametrlar deb atashimiz mumkin. Aks holda ular statik bo'lib, taklif qilingan yondashuv yordamida sozlanishi mumkin. Dinamik qayta konfiguratsiya uchun boshqa yondashuvlar kerak bo'lishi mumkin. Masalan, tizimning qismlari taqsimlangan tizimning alohida jarayonlarini qayta ishga tushirishga o'xshash tarzda yangi konfiguratsiya parametrlari bilan qayta ishga tushirilishi mumkin.
(Mening kamtarona fikrim, ish vaqtini qayta sozlashdan qochishdir, chunki bu tizimning murakkabligini oshiradi.
Qayta boshlash jarayonlari uchun OS yordamiga ishonish osonroq bo'lishi mumkin. Biroq, bu har doim ham mumkin emas.)

Statik konfiguratsiyadan foydalanishning muhim jihatlaridan biri, ba'zida odamlarni dinamik konfiguratsiyani (boshqa sabablarsiz) ko'rib chiqishga majbur qiladi - bu konfiguratsiyani yangilash vaqtida xizmat ko'rsatishning uzilish vaqti. Haqiqatan ham, agar biz statik konfiguratsiyaga o'zgartirish kiritishimiz kerak bo'lsa, yangi qiymatlar samarali bo'lishi uchun tizimni qayta ishga tushirishimiz kerak. Turli tizimlar uchun ishlamay qolish vaqtiga qo'yiladigan talablar farq qiladi, shuning uchun bu juda muhim bo'lmasligi mumkin. Agar bu juda muhim bo'lsa, biz tizimni qayta ishga tushirishni oldindan rejalashtirishimiz kerak. Masalan, biz amalga oshirishimiz mumkin AWS ELB ulanishini yo'qotish. Ushbu stsenariyda biz tizimni qayta ishga tushirishimiz kerak bo'lganda, biz tizimning yangi nusxasini parallel ravishda ishga tushiramiz, so'ngra eski tizimga mavjud ulanishlarga xizmat ko'rsatishni yakunlash uchun ELB-ni unga o'tkazamiz.

Konfiguratsiyani versiyali artefakt ichida yoki tashqarisida saqlash haqida nima deyish mumkin? Konfiguratsiyani artefakt ichida saqlash ko'p hollarda bu konfiguratsiya boshqa artefaktlar kabi sifatni ta'minlash jarayonidan o'tganligini anglatadi. Shunday qilib, konfiguratsiya sifatli va ishonchli ekanligiga ishonch hosil qilish mumkin. Aksincha, alohida fayldagi konfiguratsiya ushbu faylga kim va nima uchun o'zgartirishlar kiritilganligi haqida izlar yo'qligini anglatadi. Bu muhimmi? Ko'pgina ishlab chiqarish tizimlari uchun barqaror va yuqori sifatli konfiguratsiyaga ega bo'lish yaxshiroq deb hisoblaymiz.

Artefaktning versiyasi uning qachon yaratilganligini, u qanday qiymatlarni o'z ichiga olganligini, qanday xususiyatlar yoqilgan/o'chirilganligini, konfiguratsiyadagi har bir o'zgartirish uchun kim mas'ul ekanligini aniqlashga imkon beradi. Artefakt ichidagi konfiguratsiyani saqlash biroz harakat talab qilishi mumkin va bu dizayn tanlovidir.

Ijobiy va salbiy tomonlari

Bu erda biz taklif qilingan yondashuvning ba'zi afzalliklarini ta'kidlab, ba'zi kamchiliklarni muhokama qilmoqchimiz.

afzalliklari

To'liq taqsimlangan tizimning kompilyatsiya qilinadigan konfiguratsiyasining xususiyatlari:

  1. Konfiguratsiyani statik tekshirish. Bu yuqori darajadagi ishonchni beradi, konfiguratsiya berilgan turdagi cheklovlar to'g'ri.
  2. Konfiguratsiyaning boy tili. Odatda konfiguratsiyaning boshqa yondashuvlari eng ko'p o'zgaruvchan almashtirish bilan cheklanadi.
    Scala-dan foydalanib, konfiguratsiyani yaxshiroq qilish uchun keng ko'lamli til xususiyatlaridan foydalanish mumkin. Misol uchun, biz standart qiymatlarni taqdim qilish uchun xususiyatlardan foydalanishimiz mumkin, ob'ektlar turli doirani o'rnatish uchun, biz murojaat qilishimiz mumkin. vals tashqi doirada (QURUQ) faqat bir marta aniqlanadi. To'g'ridan-to'g'ri ketma-ketliklardan yoki ma'lum sinflarning misollaridan foydalanish mumkin (Seq, Map, Va hokazo).
  3. DSL. Scala DSL yozuvchilari uchun munosib yordamga ega. Yakuniy konfiguratsiya hech bo'lmaganda domen foydalanuvchilari tomonidan o'qilishi uchun qulayroq va oxirgi foydalanuvchi uchun qulay bo'lgan konfiguratsiya tilini yaratish uchun ushbu xususiyatlardan foydalanish mumkin.
  4. Tugunlar bo'ylab yaxlitlik va uyg'unlik. Bir joyda butun taqsimlangan tizim uchun konfiguratsiyaga ega bo'lishning afzalliklaridan biri shundaki, barcha qiymatlar qat'iy bir marta aniqlanadi va keyin bizga kerak bo'lgan barcha joylarda qayta ishlatiladi. Shuningdek, xavfsiz port deklaratsiyasini kiriting, barcha mumkin bo'lgan to'g'ri konfiguratsiyalarda tizim tugunlari bir xil tilda gaplashishiga ishonch hosil qiling. Tugunlar o'rtasida aniq bog'liqliklar mavjud bo'lib, bu ba'zi xizmatlarni taqdim etishni unutishni qiyinlashtiradi.
  5. O'zgarishlarning yuqori sifati. Oddiy PR jarayoni orqali konfiguratsiya o'zgarishlarini o'tkazishning umumiy yondashuvi konfiguratsiyada ham yuqori sifat standartlarini o'rnatadi.
  6. Bir vaqtning o'zida konfiguratsiya o'zgarishi. Biz konfiguratsiyaga har qanday o'zgartirish kiritganimizda, avtomatik joylashtirish barcha tugunlarning yangilanishini ta'minlaydi.
  7. Ilovani soddalashtirish. Ilova konfiguratsiyani tahlil qilishi va tasdiqlashi va noto'g'ri konfiguratsiya qiymatlarini boshqarishi shart emas. Bu umumiy dasturni soddalashtiradi. (Ba'zi murakkablik konfiguratsiyaning o'zida, lekin bu xavfsizlikka ongli ravishda qarama-qarshilikdir.) Oddiy konfiguratsiyaga qaytish juda oddiy - etishmayotgan qismlarni qo'shing. Kompilyatsiya qilingan konfiguratsiyani boshlash va qo'shimcha qismlarni amalga oshirishni keyinroq vaqtga qoldirish osonroq.
  8. Versiyalangan konfiguratsiya. Konfiguratsiya o'zgarishlari bir xil ishlab chiqish jarayonidan keyin sodir bo'lganligi sababli, biz noyob versiyaga ega artefaktga ega bo'lamiz. Agar kerak bo'lsa, konfiguratsiyani qayta o'zgartirishga imkon beradi. Biz hatto bir yil oldin ishlatilgan konfiguratsiyani ham o'rnatishimiz mumkin va u xuddi shunday ishlaydi. Barqaror konfiguratsiya taqsimlangan tizimning taxminiyligi va ishonchliligini yaxshilaydi. Konfiguratsiya kompilyatsiya vaqtida o'rnatiladi va ishlab chiqarish tizimida osonlikcha o'zgartirib bo'lmaydi.
  9. Modullilik. Taklif etilayotgan ramka modulli bo'lib, modullarni turli usullar bilan birlashtirish mumkin
    turli konfiguratsiyalarni qo'llab-quvvatlash (sozlash/tartib). Xususan, kichik miqyosli bitta tugunli joylashuv va keng miqyosli ko'p tugun sozlamalariga ega bo'lish mumkin. Bir nechta ishlab chiqarish sxemalariga ega bo'lish oqilona.
  10. Sinov. Sinov maqsadlarida soxta xizmatni amalga oshirish va undan xavfsiz tarzda qaramlik sifatida foydalanish mumkin. Bir vaqtning o'zida masxara bilan almashtirilgan turli qismlarga ega bo'lgan bir nechta turli xil sinov sxemalari saqlanishi mumkin.
  11. Integratsiya testi. Ba'zan taqsimlangan tizimlarda integratsiya testlarini o'tkazish qiyin. To'liq taqsimlangan tizimning xavfsiz konfiguratsiyasini yozish uchun tavsiflangan yondashuvdan foydalanib, biz barcha taqsimlangan qismlarni bitta serverda boshqariladigan tarzda ishga tushirishimiz mumkin. Vaziyatga taqlid qilish oson
    xizmatlardan biri mavjud bo'lmaganda.

Kamchiliklari

Kompilyatsiya qilingan konfiguratsiya yondashuvi "oddiy" konfiguratsiyadan farq qiladi va u barcha ehtiyojlarga mos kelmasligi mumkin. Kompilyatsiya qilingan konfiguratsiyaning ba'zi kamchiliklari:

  1. Statik konfiguratsiya. Bu barcha ilovalar uchun mos kelmasligi mumkin. Ba'zi hollarda barcha xavfsizlik choralarini chetlab o'tib, ishlab chiqarishda konfiguratsiyani tezda tuzatish zarurati tug'iladi. Ushbu yondashuv uni yanada qiyinlashtiradi. Konfiguratsiyaga har qanday o'zgartirish kiritilgandan so'ng kompilyatsiya va qayta joylashtirish talab qilinadi. Bu ham xususiyat, ham yuk.
  2. Konfiguratsiyani yaratish. Konfiguratsiya ba'zi avtomatlashtirish vositalari tomonidan yaratilganda, bu yondashuv keyingi kompilyatsiyani talab qiladi (bu o'z navbatida muvaffaqiyatsiz bo'lishi mumkin). Ushbu qo'shimcha qadamni qurish tizimiga integratsiya qilish uchun qo'shimcha harakat talab qilinishi mumkin.
  3. Asboblar. Bugungi kunda matnga asoslangan konfiguratsiyalarga tayanadigan ko'plab vositalar qo'llanilmoqda. Ulardan ba'zilari
    konfiguratsiya tuzilganda qo'llanilmaydi.
  4. Fikrlashda o'zgarish kerak. Ishlab chiquvchilar va DevOps matn konfiguratsiya fayllari bilan yaxshi tanish. Konfiguratsiyani tuzish g'oyasi ularga g'alati tuyulishi mumkin.
  5. Kompilyatsiya qilinadigan konfiguratsiyani joriy etishdan oldin yuqori sifatli dasturiy ta'minotni ishlab chiqish jarayoni talab qilinadi.

Amalga oshirilgan misolda ba'zi cheklovlar mavjud:

  1. Agar biz tugunni amalga oshirish talab qilmaydigan qo'shimcha konfiguratsiyani taqdim qilsak, kompilyator bizga mavjud bo'lmagan dasturni aniqlashga yordam bermaydi. Buni foydalanish orqali hal qilish mumkin HList yoki ADTs (holat sinflari) belgilar va Cake Pattern o'rniga tugun konfiguratsiyasi uchun.
  2. Biz konfiguratsiya faylida ba'zi qozonlarni taqdim etishimiz kerak: (package, import, object deklaratsiyalar;
    override defstandart qiymatlarga ega bo'lgan parametrlar uchun). Bu qisman DSL yordamida hal qilinishi mumkin.
  3. Ushbu postda biz shunga o'xshash tugunlarning klasterlarini dinamik qayta konfiguratsiyani qamrab olmaymiz.

Xulosa

Ushbu postda biz konfiguratsiyani to'g'ridan-to'g'ri manba kodida xavfsiz tarzda taqdim etish g'oyasini muhokama qildik. Ushbu yondashuv ko'plab ilovalarda xml va boshqa matnga asoslangan konfiguratsiyalarni almashtirish sifatida ishlatilishi mumkin. Bizning misolimiz Scala-da amalga oshirilganiga qaramay, uni boshqa kompilyatsiya qilinadigan tillarga ham tarjima qilish mumkin (masalan, Kotlin, C#, Swift va boshqalar). Ushbu yondashuvni yangi loyihada sinab ko'rish mumkin va agar u yaxshi mos kelmasa, eski uslubga o'tish mumkin.

Albatta, kompilyatsiya qilinadigan konfiguratsiya yuqori sifatli ishlab chiqish jarayonini talab qiladi. Buning evaziga u bir xil darajada yuqori sifatli mustahkam konfiguratsiyani taqdim etishni va'da qiladi.

Ushbu yondashuv turli yo'llar bilan kengaytirilishi mumkin:

  1. Konfiguratsiyani tekshirishni amalga oshirish uchun makrolardan foydalanish mumkin va har qanday biznes-mantiqiy cheklovlar muvaffaqiyatsizlikka uchragan taqdirda kompilyatsiya vaqtida muvaffaqiyatsizlikka uchraydi.
  2. Konfiguratsiyani domenga qulay tarzda ifodalash uchun DSL amalga oshirilishi mumkin.
  3. Konfiguratsiyani avtomatik sozlash bilan dinamik resurslarni boshqarish. Masalan, biz klaster tugunlari sonini sozlaganimizda (1) tugunlar biroz o'zgartirilgan konfiguratsiyaga ega bo'lishini xohlashimiz mumkin; (2) yangi tugunlar haqida ma'lumot olish uchun klaster menejeri.

minnatdorchilik

Men Andrey Saksonov, Pavel Popov, Anton Nehaevga ushbu post loyihasi bo'yicha ilhomlantiruvchi fikr-mulohazalarini bildirganliklari uchun rahmat aytmoqchiman, bu menga uni yanada aniqroq qilishimga yordam berdi.

Manba: www.habr.com