Paylanmış sistemin tərtib edilə bilən konfiqurasiyası

Bu yazıda paylanmış sistemin konfiqurasiyası ilə məşğul olmağın maraqlı üsulunu bölüşmək istərdik.
Konfiqurasiya birbaşa Scala dilində təhlükəsiz tipdə təqdim olunur. İcra nümunəsi ətraflı təsvir edilmişdir. Təklifin müxtəlif aspektləri, o cümlədən ümumi inkişaf prosesinə təsiri müzakirə olunur.

Paylanmış sistemin tərtib edilə bilən konfiqurasiyası

(rus dilində)

giriş

Güclü paylanmış sistemlərin qurulması bütün qovşaqlarda düzgün və ardıcıl konfiqurasiyanın istifadəsini tələb edir. Tipik bir həll mətn yerləşdirmə təsvirindən (terraform, ansible və ya oxşar bir şey) və avtomatik olaraq yaradılan konfiqurasiya fayllarından (çox vaxt hər bir node/rol üçün ayrılmış) istifadə etməkdir. Biz həmçinin hər bir əlaqə quran qovşaqda eyni versiyaların eyni protokollarından istifadə etmək istərdik (əks halda uyğunsuzluq problemləri ilə üzləşərdik). JVM dünyasında bu o deməkdir ki, ən azı mesajlaşma kitabxanası bütün kommunikasiya qovşaqlarında eyni versiyada olmalıdır.

Sistemi sınamaq haqqında nə demək olar? Əlbəttə ki, inteqrasiya testlərinə gəlməzdən əvvəl bütün komponentlər üçün vahid testlərimiz olmalıdır. Test nəticələrini icra müddətində ekstrapolyasiya edə bilmək üçün bütün kitabxanaların versiyalarının həm iş vaxtı, həm də sınaq mühitlərində eyni saxlandığından əmin olmalıyıq.

İnteqrasiya testlərini həyata keçirərkən, bütün qovşaqlarda eyni sinif yolunun olması çox vaxt daha asandır. Biz yalnız eyni sinif yolunun yerləşdirmədə istifadə olunduğundan əmin olmalıyıq. (Müxtəlif qovşaqlarda müxtəlif sinif yollarından istifadə etmək mümkündür, lakin bu konfiqurasiyanı təmsil etmək və onu düzgün yerləşdirmək daha çətindir.) Beləliklə, hər şeyi sadə saxlamaq üçün biz bütün qovşaqlarda yalnız eyni sinif yollarını nəzərdən keçirəcəyik.

Konfiqurasiya proqram təminatı ilə birlikdə inkişaf etməyə meyllidir. Biz adətən müxtəlifliyi müəyyən etmək üçün versiyalardan istifadə edirik
proqram təminatının təkamül mərhələləri. Versiya idarəetməsi altında konfiqurasiyanı əhatə etmək və bəzi etiketlərlə müxtəlif konfiqurasiyaları müəyyən etmək məqsədəuyğun görünür. İstehsalda yalnız bir konfiqurasiya varsa, identifikator kimi tək versiyadan istifadə edə bilərik. Bəzən bir neçə istehsal mühitimiz ola bilər. Və hər bir mühit üçün ayrıca konfiqurasiya şöbəsinə ehtiyacımız ola bilər. Beləliklə, müxtəlif konfiqurasiyaları unikal şəkildə müəyyən etmək üçün konfiqurasiyalar filial və versiya ilə etiketlənə bilər. Hər bir filial etiketi və versiyası paylanmış qovşaqların, portların, xarici resursların, hər node üzrə sinif yolu kitabxanası versiyalarının vahid kombinasiyasına uyğundur. Burada biz yalnız bir filialı əhatə edəcəyik və konfiqurasiyaları digər artefaktlar kimi üç komponentli onluq versiya (1.2.3) ilə müəyyən edəcəyik.

Müasir mühitlərdə konfiqurasiya faylları artıq əl ilə dəyişdirilmir. Adətən biz yaradırıq
yerləşdirmə zamanı konfiqurasiya faylları və onlara heç vaxt toxunma sonra. Beləliklə, soruşa bilərsiniz ki, niyə konfiqurasiya faylları üçün hələ də mətn formatından istifadə edirik? Münasib seçim, konfiqurasiyanı kompilyasiya vahidinin içərisinə yerləşdirmək və kompilyasiya vaxtı konfiqurasiyanın təsdiqindən faydalanmaqdır.

Bu yazıda biz tərtib edilmiş artefaktda konfiqurasiyanın saxlanması ideyasını araşdıracağıq.

Kompilyasiya edilə bilən konfiqurasiya

Bu bölmədə biz statik konfiqurasiya nümunəsini müzakirə edəcəyik. İki sadə xidmət - əks-səda xidməti və əks-səda xidmətinin müştərisi konfiqurasiya edilir və tətbiq edilir. Sonra hər iki xidmətlə iki fərqli paylanmış sistem yaradılır. Biri tək qovşaq konfiqurasiyası, digəri isə iki qovşaq konfiqurasiyası üçündür.

Tipik paylanmış sistem bir neçə qovşaqdan ibarətdir. Düyünlər bəzi növlərdən istifadə etməklə müəyyən edilə bilər:

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

və ya yalnız

case class NodeId(hostName: String)

və ya hətta

object Singleton
type NodeId = Singleton.type

Bu qovşaqlar müxtəlif rolları yerinə yetirir, bəzi xidmətləri idarə edir və TCP/HTTP bağlantıları vasitəsilə digər qovşaqlarla əlaqə qura bilməlidir.

TCP bağlantısı üçün ən azı port nömrəsi tələb olunur. Biz həmçinin müştəri və serverin eyni protokolla danışdığından əmin olmaq istəyirik. Düyünlər arasında əlaqəni modelləşdirmək üçün aşağıdakı sinfi elan edək:

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

hara Port yalnız bir Int icazə verilən diapazonda:

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

Təmizlənmiş növlər

Görmək nəfis kitabxana. Bir sözlə, digər növlərə kompilyasiya vaxtı məhdudiyyətləri əlavə etməyə imkan verir. Bu halda Int yalnız port nömrəsini təmsil edə bilən 16 bitlik dəyərlərə icazə verilir. Bu konfiqurasiya yanaşması üçün bu kitabxanadan istifadə etmək tələbi yoxdur. Sadəcə çox yaxşı uyğun gəlir.

HTTP (REST) ​​üçün bizə xidmət yolu da lazım ola bilər:

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

Fantom növü

Kompilyasiya zamanı protokolu müəyyən etmək üçün növ arqumentinin elan edilməsinin Scala xüsusiyyətindən istifadə edirik. Protocol sinifdə istifadə olunmur. Bu qondarma fantom növü. İş vaxtında bizə nadir hallarda protokol identifikatorunun nümunəsinə ehtiyacımız olur, ona görə də onu saxlamırıq. Kompilyasiya zamanı bu fantom tip əlavə tip təhlükəsizliyi təmin edir. Biz portu səhv protokolla ötürə bilmirik.

Ən çox istifadə olunan protokollardan biri Json serializasiyası ilə REST API-dir:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

hara RequestMessage müştərinin serverə göndərə biləcəyi əsas mesaj növüdür və ResponseMessage serverdən gələn cavab mesajıdır. Əlbəttə ki, əlaqə protokolunu istənilən dəqiqliklə təyin edən digər protokol təsvirləri yarada bilərik.

Bu yazının məqsədləri üçün protokolun daha sadə versiyasından istifadə edəcəyik:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Bu protokolda sorğu mesajı url-ə əlavə olunur və cavab mesajı düz sətir kimi qaytarılır.

Xidmət konfiqurasiyası xidmət adı, portlar toplusu və bəzi asılılıqlarla təsvir edilə bilər. Bütün bu elementləri Scala-da necə təmsil etməyin bir neçə mümkün yolu var (məsələn, HList, cəbri məlumat növləri). Bu yazının məqsədləri üçün biz Tort Nümunəsindən istifadə edəcəyik və birləşdirilə bilən parçaları (modulları) əlamətlər kimi təqdim edəcəyik. (Cake Pattern bu tərtib edilə bilən konfiqurasiya yanaşması üçün tələb deyil. Bu, ideyanın sadəcə mümkün həyata keçirilməsidir.)

Asılılıqlar digər qovşaqların son nöqtələri kimi Tort nümunəsindən istifadə etməklə təmsil oluna bilər:

  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 xidmətinə yalnız konfiqurasiya edilmiş port lazımdır. Və bildiririk ki, bu port echo protokolunu dəstəkləyir. Nəzərə alın ki, hazırda xüsusi bir port göstərməyimizə ehtiyac yoxdur, çünki əlamətlər mücərrəd metodlar bəyannaməsinə imkan verir. Mücərrəd üsullardan istifadə etsək, kompilyator konfiqurasiya nümunəsində tətbiq tələb edəcək. Burada icrasını təmin etdik (8081) və onu konkret konfiqurasiyada atlasaq, standart dəyər kimi istifadə olunacaq.

Echo xidmət müştərisinin konfiqurasiyasında asılılıq elan edə bilərik:

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

Asılılıq ilə eyni tip var echoService. Xüsusilə də eyni protokolu tələb edir. Beləliklə, bu iki asılılığı birləşdirsək, onların düzgün işləyəcəyinə əmin ola bilərik.

Xidmətlərin həyata keçirilməsi

Xidmətə başlamaq və zərif şəkildə bağlanmaq üçün funksiya lazımdır. (Xidməti dayandırmaq imkanı sınaq üçün vacibdir.) Yenə də verilmiş konfiqurasiya üçün belə funksiyanın təyin edilməsinin bir neçə variantı var (məsələn, biz tip siniflərindən istifadə edə bilərik). Bu yazı üçün biz yenidən Tort Nümunəsindən istifadə edəcəyik. istifadə edərək bir xidməti təmsil edə bilərik cats.Resource bu artıq mötərizə və resursun buraxılmasını təmin edir. Resurs əldə etmək üçün konfiqurasiya və bəzi iş vaxtı kontekstini təqdim etməliyik. Beləliklə, xidmətin başlanğıc funksiyası belə görünə bilər:

  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]
  }

hara

  • Config — bu xidmət başlanğıcının tələb etdiyi konfiqurasiya növü
  • AddressResolver — digər qovşaqların real ünvanlarını əldə etmək imkanı olan iş vaxtı obyekti (ətraflı məlumat üçün oxumağa davam edin).

digər növlər gəlir cats:

  • F[_] — təsir növü (Ən sadə halda F[A] ədalətli ola bilər () => A. Bu yazıda istifadə edəcəyik cats.IO.)
  • Reader[A,B] — funksiyanın az-çox sinonimidir A => B
  • cats.Resource — əldə etmək və buraxmaq yolları var
  • Timer — yatmağa/vaxt ölçməyə imkan verir
  • ContextShift - analoqu ExecutionContext
  • Applicative — qüvvədə olan funksiyaların sarğısı (demək olar ki, monad) (sonda onu başqa bir şeylə əvəz edə bilərik)

Bu interfeysdən istifadə edərək bir neçə xidməti həyata keçirə bilərik. Məsələn, heç bir şey etməyən bir xidmət:

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

(Bax Mənbə kodu digər xidmətlərin tətbiqi üçün - əks-səda xidməti,
echo müştəriömür boyu nəzarətçilər.)

Bir qovşaq bir neçə xidməti işlədən tək obyektdir (resurslar zəncirinin başlanğıcı Cake Pattern tərəfindən aktivləşdirilir):

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

Qeyd edək ki, qovşaqda bu node üçün lazım olan dəqiq konfiqurasiya növünü təyin edirik. Kompilyator bizə qeyri-kafi tipli obyekti (Cake) tikməyə icazə verməyəcək, çünki hər bir xidmət xüsusiyyəti məhdudiyyət elan edir. Config növü. Həmçinin tam konfiqurasiya təmin etmədən qovşağı başlada bilməyəcəyik.

Node ünvanının həlli

Əlaqə yaratmaq üçün hər bir qovşaq üçün real host ünvanı lazımdır. Bu, konfiqurasiyanın digər hissələrindən daha gec məlum ola bilər. Beləliklə, node id və onun faktiki ünvanı arasında xəritəni təqdim etmək üçün bir yola ehtiyacımız var. Bu xəritələşdirmə funksiyasıdır:

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

Belə bir funksiyanı həyata keçirməyin bir neçə mümkün yolu var.

  1. Əgər biz yerləşdirmədən əvvəl, node hostlarının yaradılması zamanı faktiki ünvanları biliriksə, o zaman biz faktiki ünvanlarla Scala kodu yarada və daha sonra quruluşu işə sala bilərik (bu, tərtib vaxtının yoxlanılmasını həyata keçirir və sonra inteqrasiya test paketini işə salır). Bu halda bizim xəritələşdirmə funksiyamız statik olaraq tanınır və a kimi bir şeyə sadələşdirilə bilər Map[NodeId, NodeAddress].
  2. Bəzən biz faktiki ünvanları yalnız qovşaq həqiqətən işə salındıqda əldə edirik və ya hələ işə salınmamış qovşaqların ünvanları yoxdur. Bu halda, bütün digər qovşaqlardan əvvəl işə salınan kəşf xidmətimiz ola bilər və hər bir qovşaq həmin xidmətdə öz ünvanını reklam edə və asılılıqlara abunə ola bilər.
  3. Dəyişdirə bilsək /etc/hosts, biz əvvəlcədən təyin edilmiş host adlarından istifadə edə bilərik (məsələn my-project-main-nodeecho-backend) və sadəcə bu adı yerləşdirmə zamanı ip ünvanı ilə əlaqələndirin.

Bu yazıda bu halları daha ətraflı əhatə etmirik. Əslində oyuncaq nümunəmizdə bütün qovşaqlar eyni IP ünvanına sahib olacaq - 127.0.0.1.

Bu yazıda iki paylanmış sistem planını nəzərdən keçirəcəyik:

  1. Bütün xidmətlərin tək node üzərində yerləşdirildiyi tək qovşaq düzümü.
  2. Xidmət və müştərinin fərqli qovşaqlarda olduğu iki node layout.

a üçün konfiqurasiya tək düyün layout aşağıdakı kimidir:

Tək node konfiqurasiyası

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.
}

Burada həm server, həm də müştəri konfiqurasiyasını genişləndirən vahid konfiqurasiya yaradırıq. Həmçinin biz müştəri və serveri normal olaraq dayandıracaq bir həyat dövrü nəzarətçisini konfiqurasiya edirik lifetime interval keçir.

Eyni xidmət proqramları və konfiqurasiyaları iki ayrı qovşaqdan ibarət sistemin tərtibatını yaratmaq üçün istifadə edilə bilər. Bizə sadəcə yaratmaq lazımdır iki ayrı node konfiqurasiyası müvafiq xidmətlərlə:

İki qovşaq konfiqurasiyası

  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"
  }

Asılılığı necə təyin etdiyimizə baxın. Cari node asılılığı kimi digər qovşağın təqdim etdiyi xidməti qeyd edirik. Asılılıq növü yoxlanılır, çünki o, protokolu təsvir edən fantom növü ehtiva edir. Və iş vaxtında biz düzgün node id-ə sahib olacağıq. Bu, təklif olunan konfiqurasiya yanaşmasının mühüm aspektlərindən biridir. Bu, bizə portu yalnız bir dəfə təyin etmək və düzgün porta istinad etdiyimizə əmin olmaq imkanı verir.

İki node həyata keçirilməsi

Bu konfiqurasiya üçün biz tam olaraq eyni xidmət proqramlarından istifadə edirik. Heç bir dəyişiklik yoxdur. Bununla belə, biz müxtəlif xidmətlər dəstini ehtiva edən iki fərqli node tətbiqi yaradırıq:

  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
  }

Birinci node server həyata keçirir və ona yalnız server tərəfi konfiqurasiyası lazımdır. İkinci node müştərini həyata keçirir və konfiqurasiyanın başqa hissəsinə ehtiyac duyur. Hər iki qovşaq müəyyən ömür boyu spesifikasiya tələb edir. Bu poçt xidməti qovşağının məqsədləri üçün istifadə edərək dayandırıla bilən sonsuz xidmət müddəti olacaq SIGTERM, echo müştəri konfiqurasiya edilmiş sonlu müddətdən sonra dayandırılacaq. baxın başlanğıc tətbiqi ətraflı məlumat üçün.

Ümumi inkişaf prosesi

Bu yanaşmanın konfiqurasiya ilə işləmə tərzimizi necə dəyişdirdiyini görək.

Kod kimi konfiqurasiya tərtib ediləcək və artefakt yaradacaq. Konfiqurasiya artefaktını digər kod artefaktlarından ayırmaq məqsədəuyğun görünür. Çox vaxt eyni kod bazasında çoxlu konfiqurasiyaya sahib ola bilərik. Və əlbəttə ki, müxtəlif konfiqurasiya filiallarının bir neçə versiyası ola bilər. Konfiqurasiyada biz kitabxanaların xüsusi versiyalarını seçə bilərik və bu konfiqurasiyanı yerləşdirdiyimiz zaman bu sabit qalacaq.

Konfiqurasiya dəyişikliyi kod dəyişikliyinə çevrilir. Beləliklə, o, eyni keyfiyyət təminatı prosesi ilə əhatə olunmalıdır:

Bilet -> PR -> nəzərdən keçirin -> birləşdirin -> davamlı inteqrasiya -> davamlı yerləşdirmə

Bu yanaşmanın aşağıdakı nəticələri var:

  1. Konfiqurasiya müəyyən bir sistemin nümunəsi üçün uyğundur. Deyəsən, qovşaqlar arasında səhv əlaqə qurmağın heç bir yolu yoxdur.
  2. Yalnız bir qovşaqda konfiqurasiyanı dəyişdirmək asan deyil. Bəzi mətn fayllarını daxil etmək və dəyişdirmək ağlabatan deyil. Beləliklə, konfiqurasiya sürüşməsi daha az mümkün olur.
  3. Kiçik konfiqurasiya dəyişiklikləri etmək asan deyil.
  4. Konfiqurasiya dəyişikliklərinin əksəriyyəti eyni inkişaf prosesini izləyəcək və bəzi baxışlardan keçəcək.

İstehsal konfiqurasiyası üçün ayrıca depoya ehtiyacımız varmı? İstehsal konfiqurasiyası bir çox insanın əlçatmaz olmasını istədiyimiz həssas məlumatları ehtiva edə bilər. Beləliklə, istehsal konfiqurasiyasını ehtiva edən məhdud girişi olan ayrıca bir depo saxlamağa dəyər ola bilər. Biz konfiqurasiyanı iki hissəyə ayıra bilərik - biri istehsalın ən açıq parametrlərini, digəri isə konfiqurasiyanın gizli hissəsini ehtiva edir. Bu, həqiqətən həssas şeylərə girişi məhdudlaşdırarkən, tərtibatçıların əksəriyyətinə parametrlərin böyük əksəriyyətinə giriş imkanı verəcəkdir. Defolt parametr dəyərlərinə malik ara əlamətlərdən istifadə edərək bunu yerinə yetirmək asandır.

Varyasyon

Digər konfiqurasiya idarəetmə üsulları ilə müqayisədə təklif olunan yanaşmanın müsbət və mənfi cəhətlərini görək.

Əvvəlcə konfiqurasiya ilə məşğul olmağın təklif olunan müxtəlif aspektlərinə bir neçə alternativ sadalayacağıq:

  1. Hədəf maşındakı mətn faylı.
  2. Mərkəzləşdirilmiş açar-dəyər yaddaşı (məsələn etcd/zookeeper).
  3. Prosesi yenidən başlatmadan yenidən konfiqurasiya edilə/yenidən işə salına bilən alt proses komponentləri.
  4. Artefakt və versiya nəzarətindən kənar konfiqurasiya.

Mətn faylı ad-hoc düzəlişləri baxımından bəzi rahatlıq verir. Sistemin administratoru hədəf node daxil ola, dəyişiklik edə və sadəcə xidməti yenidən başlada bilər. Bu daha böyük sistemlər üçün yaxşı olmaya bilər. Dəyişikliyin arxasında heç bir iz qalmır. Dəyişiklik başqa bir cüt göz tərəfindən nəzərdən keçirilmir. Dəyişikliyə nəyin səbəb olduğunu tapmaq çətin ola bilər. Test olunmayıb. Paylanmış sistem nöqteyi-nəzərindən idarəçi sadəcə olaraq digər qovşaqlardan birində konfiqurasiyanı yeniləməyi unuda bilər.

(Btw, əgər nəticədə mətn konfiqurasiya fayllarından istifadə etməyə başlamağa ehtiyac yaranarsa, biz yalnız eyni şeyi yarada biləcək təhliledici + təsdiqləyici əlavə etməliyik. Config yazın və bu mətn konfiqurasiyalarından istifadə etməyə başlamaq üçün kifayətdir. Bu həm də onu göstərir ki, kompilyasiya vaxtı konfiqurasiyasının mürəkkəbliyi mətn əsaslı konfiqurasiyaların mürəkkəbliyindən bir qədər kiçikdir, çünki mətn əsaslı versiyada bizə əlavə kod lazımdır.)

Mərkəzləşdirilmiş açar-dəyər yaddaşı tətbiq meta parametrlərinin paylanması üçün yaxşı mexanizmdir. Burada konfiqurasiya dəyərləri hesab etdiyimiz şeyləri və yalnız məlumatların nə olduğunu düşünməliyik. Funksiya verilmişdir C => A => B biz adətən nadir hallarda dəyişən dəyərlər adlandırırıq C "konfiqurasiya", məlumatları tez-tez dəyişdirərkən A - sadəcə məlumat daxil edin. Konfiqurasiya funksiyaya verilənlərdən əvvəl təqdim edilməlidir A. Bu fikri nəzərə alaraq deyə bilərik ki, bu, konfiqurasiya məlumatlarını sadəcə məlumatlardan ayırmaq üçün istifadə edilə bilən dəyişikliklərin gözlənilən tezliyidir. Həmçinin məlumatlar adətən bir mənbədən (istifadəçi) və konfiqurasiya fərqli mənbədən (admin) gəlir. Başlama prosesindən sonra dəyişdirilə bilən parametrlərlə məşğul olmaq tətbiqin mürəkkəbliyinin artmasına səbəb olur. Bu cür parametrlər üçün onların çatdırılma mexanizmini, təhlilini və təsdiqini, yanlış dəyərləri idarə etməli olacağıq. Beləliklə, proqramın mürəkkəbliyini azaltmaq üçün işləmə zamanı dəyişə bilən parametrlərin sayını azaltmaq (və ya hətta onları tamamilə aradan qaldırmaq) daha yaxşıdır.

Bu yazının perspektivindən biz statik və dinamik parametrlər arasında fərq qoymalıyıq. Əgər xidmət məntiqi işləmə zamanı bəzi parametrlərin nadir hallarda dəyişdirilməsini tələb edirsə, biz onları dinamik parametrlər adlandıra bilərik. Əks halda, onlar statikdir və təklif olunan yanaşma ilə konfiqurasiya edilə bilər. Dinamik yenidən konfiqurasiya üçün başqa yanaşmalara ehtiyac ola bilər. Məsələn, paylanmış sistemin ayrı-ayrı proseslərini yenidən işə salmağa bənzər şəkildə sistemin hissələri yeni konfiqurasiya parametrləri ilə yenidən işə salına bilər.
(Mənim təvazökar fikrim, sistemin mürəkkəbliyini artırdığı üçün iş vaxtının yenidən konfiqurasiyasına yol verməməkdir.
Proseslərin yenidən başlaması üçün yalnız OS dəstəyinə etibar etmək daha sadə ola bilər. Baxmayaraq ki, bu, həmişə mümkün olmaya bilər.)

Statik konfiqurasiyadan istifadənin bəzən insanları dinamik konfiqurasiyanı (başqa səbəblər olmadan) nəzərdən keçirməyə vadar edən mühüm cəhətlərindən biri konfiqurasiya yeniləməsi zamanı xidmətin dayandırılmasıdır. Həqiqətən, statik konfiqurasiyada dəyişiklik etməli olsaq, yeni dəyərlərin təsirli olması üçün sistemi yenidən başlatmalıyıq. Fərqli sistemlər üçün dayanma müddəti tələbləri dəyişir, ona görə də o qədər də kritik olmaya bilər. Əgər kritikdirsə, o zaman hər hansı bir sistemin yenidən başlaması üçün əvvəlcədən planlaşdırmalıyıq. Məsələn, biz həyata keçirə bilərdik AWS ELB bağlantısının boşaldılması. Bu ssenaridə sistemi hər dəfə yenidən işə salmaq lazım olduqda, biz paralel olaraq sistemin yeni nümunəsini işə salırıq, sonra ELB-ni ona keçirik və köhnə sistemə mövcud əlaqələrə xidmət göstərməyi tamamlamağa icazə veririk.

Konfiqurasiyanı versiyalı artefakt daxilində və ya kənarda saxlamaq haqqında nə demək olar? Konfiqurasiyanın artefakt daxilində saxlanması əksər hallarda bu konfiqurasiyanın digər artefaktlarla eyni keyfiyyət təminatı prosesindən keçməsi deməkdir. Beləliklə, konfiqurasiyanın keyfiyyətli və etibarlı olduğuna əmin ola bilərsiniz. Əksinə, ayrı bir faylda konfiqurasiya o deməkdir ki, bu faylda kimin və nə üçün dəyişikliklər edildiyinə dair heç bir iz yoxdur. Bu vacibdirmi? Biz hesab edirik ki, əksər istehsal sistemləri üçün sabit və yüksək keyfiyyətli konfiqurasiyaya malik olmaq daha yaxşıdır.

Artefaktın versiyası onun nə vaxt yaradıldığını, hansı dəyərləri ehtiva etdiyini, hansı xüsusiyyətlərin aktivləşdirildiyini/deaktiv edildiyini, konfiqurasiyada hər bir dəyişikliyə kimin cavabdeh olduğunu öyrənməyə imkan verir. Artefakt daxilində konfiqurasiyanı saxlamaq üçün müəyyən səy tələb oluna bilər və bu, dizayn seçimidir.

Artıq və mənfi cəhətlər

Burada təklif olunan yanaşmanın bəzi üstünlüklərini qeyd etmək və bəzi çatışmazlıqları müzakirə etmək istərdik.

Üstünlüklər

Tam paylanmış sistemin tərtib edilə bilən konfiqurasiyasının xüsusiyyətləri:

  1. Konfiqurasiyanın statik yoxlanışı. Bu, tip məhdudiyyətləri nəzərə alınmaqla konfiqurasiyanın düzgün olduğuna yüksək səviyyədə əminlik verir.
  2. Zəngin konfiqurasiya dili. Tipik olaraq, digər konfiqurasiya yanaşmaları ən çox dəyişən əvəzetmə ilə məhdudlaşır.
    Scala istifadə edərək konfiqurasiyanı daha yaxşı etmək üçün geniş dil xüsusiyyətlərindən istifadə edə bilərsiniz. Məsələn, standart dəyərləri təmin etmək üçün əlamətlərdən, fərqli əhatə dairəsini təyin etmək üçün obyektlərdən istifadə edə bilərik. vals xarici əhatə dairəsində (QURU) yalnız bir dəfə müəyyən edilir. Hərfi ardıcıllıqlardan və ya müəyyən siniflərin nümunələrindən istifadə etmək mümkündür (Seq, Map, Və sairə).
  3. DSL. Scala DSL yazıçıları üçün layiqli dəstəyə malikdir. Son konfiqurasiyanın ən azı domen istifadəçiləri tərəfindən oxuna bilməsi üçün daha rahat və son istifadəçi dostu olan konfiqurasiya dili yaratmaq üçün bu xüsusiyyətlərdən istifadə etmək olar.
  4. Düyünlər arasında bütövlük və uyğunluq. Bütün paylanmış sistem üçün konfiqurasiyanın bir yerdə olmasının üstünlüklərindən biri, bütün dəyərlərin ciddi şəkildə bir dəfə müəyyən edilməsi və sonra ehtiyac duyduğumuz bütün yerlərdə yenidən istifadə edilməsidir. Həmçinin təhlükəsiz port bəyannamələrini yazın, bütün mümkün düzgün konfiqurasiyalarda sistemin qovşaqlarının eyni dildə danışacağını təmin edin. Qovşaqlar arasında açıq-aşkar asılılıqlar var ki, bu da bəzi xidmətləri göstərməyi unutmağı çətinləşdirir.
  5. Dəyişikliklərin yüksək keyfiyyəti. Konfiqurasiya dəyişikliklərinin normal PR prosesindən keçməsinə ümumi yanaşma konfiqurasiyada da yüksək keyfiyyət standartlarını müəyyən edir.
  6. Eyni vaxtda konfiqurasiya dəyişiklikləri. Biz konfiqurasiyada hər hansı dəyişiklik etdikdə avtomatik yerləşdirmə bütün qovşaqların yenilənməsini təmin edir.
  7. Tətbiqin sadələşdirilməsi. Tətbiqin konfiqurasiyanın təhlili və təsdiqlənməsi və yanlış konfiqurasiya dəyərlərinin idarə edilməsinə ehtiyac yoxdur. Bu, ümumi tətbiqi asanlaşdırır. (Bəzi mürəkkəblik artımı konfiqurasiyanın özündədir, lakin bu, təhlükəsizliyə qarşı şüurlu bir güzəştdir.) Adi konfiqurasiyaya qayıtmaq olduqca sadədir – sadəcə çatışmayan hissələri əlavə edin. Tərtib edilmiş konfiqurasiyaya başlamaq və əlavə hissələrin həyata keçirilməsini bir qədər sonraya təxirə salmaq daha asandır.
  8. Versiyalaşdırılmış konfiqurasiya. Konfiqurasiya dəyişiklikləri eyni inkişaf prosesini izlədiyinə görə, nəticədə unikal versiyaya malik artefakt əldə edirik. Lazım gələrsə, konfiqurasiyanı geri qaytarmağa imkan verir. Hətta bir il əvvəl istifadə edilən konfiqurasiyanı yerləşdirə bilərik və o, eyni şəkildə işləyəcək. Sabit konfiqurasiya paylanmış sistemin proqnozlaşdırıla bilməsini və etibarlılığını artırır. Konfiqurasiya tərtib zamanı müəyyən edilir və istehsal sistemində asanlıqla dəyişdirilə bilməz.
  9. Modulluq. Təklif olunan çərçivə moduldur və modullar müxtəlif yollarla birləşdirilə bilər
    müxtəlif konfiqurasiyaları dəstəkləyir (quraşdırmalar/layouts). Xüsusilə, kiçik miqyaslı tək node layout və geniş miqyaslı çox node qəbulu mümkündür. Çoxsaylı istehsal planlarının olması məqsədəuyğundur.
  10. Test. Sınaq məqsədləri üçün saxta bir xidmət tətbiq edə və onu təhlükəsiz bir şəkildə asılılıq kimi istifadə edə bilərsiniz. Müxtəlif hissələri istehza ilə əvəz olunan bir neçə fərqli sınaq planı eyni vaxtda saxlanıla bilər.
  11. İnteqrasiya testi. Bəzən paylanmış sistemlərdə inteqrasiya testlərini həyata keçirmək çətindir. Tam paylanmış sistemin təhlükəsiz konfiqurasiyasını yazmaq üçün təsvir edilən yanaşmadan istifadə edərək, biz bütün paylanmış hissələri bir serverdə idarə olunan şəkildə işlədə bilərik. Vəziyyəti təqlid etmək asandır
    xidmətlərdən biri əlçatmaz olduqda.

Dezavantajları

Tərtib edilmiş konfiqurasiya yanaşması “normal” konfiqurasiyadan fərqlidir və bütün ehtiyaclara cavab verməyə bilər. Tərtib edilmiş konfiqurasiyanın bəzi çatışmazlıqları bunlardır:

  1. Statik konfiqurasiya. Bütün tətbiqlər üçün uyğun olmaya bilər. Bəzi hallarda bütün təhlükəsizlik tədbirlərindən yan keçərək istehsalda konfiqurasiyanı tez bir zamanda düzəltməyə ehtiyac var. Bu yanaşma işi daha da çətinləşdirir. Konfiqurasiyada hər hansı dəyişiklik edildikdən sonra kompilyasiya və yenidən yerləşdirmə tələb olunur. Bu həm xüsusiyyət, həm də yükdür.
  2. Konfiqurasiya generasiyası. Konfiqurasiya bəzi avtomatlaşdırma alətləri tərəfindən yaradıldıqda, bu yanaşma sonrakı tərtibi tələb edir (bu da öz növbəsində uğursuz ola bilər). Bu əlavə addımı quraşdırma sisteminə inteqrasiya etmək üçün əlavə səy tələb oluna bilər.
  3. Alətlər. Bu gün mətn əsaslı konfiqurasiyalara əsaslanan çoxlu alətlər istifadə olunur. Onlardan bəziləri
    konfiqurasiya tərtib edildikdə tətbiq edilməyəcək.
  4. Düşüncədə dəyişiklik lazımdır. Tərtibatçılar və DevOps mətn konfiqurasiya faylları ilə tanışdırlar. Konfiqurasiyanın tərtib edilməsi ideyası onlara qəribə görünə bilər.
  5. Tərtib edilə bilən konfiqurasiyanı təqdim etməzdən əvvəl yüksək keyfiyyətli proqram təminatının hazırlanması prosesi tələb olunur.

Həyata keçirilən nümunənin bəzi məhdudiyyətləri var:

  1. Əgər biz node tətbiqi tərəfindən tələb olunmayan əlavə konfiqurasiya təqdim etsək, kompilyator bizə olmayan icranı aşkarlamağa kömək etməyəcək. Bu, istifadə etməklə həll edilə bilər HList və ya ADT-lər (hal sinifləri) əlamətlər və Cake Pattern əvəzinə node konfiqurasiyası üçün.
  2. Biz konfiqurasiya faylında bəzi boşqab təqdim etməliyik: (package, import, object bəyannamələr;
    override def's default dəyərləri olan parametrlər üçün). Bu DSL istifadə edərək qismən həll edilə bilər.
  3. Bu yazıda biz oxşar qovşaqların klasterlərinin dinamik yenidən konfiqurasiyasını əhatə etmirik.

Nəticə

Bu yazıda konfiqurasiyanı birbaşa mənbə kodunda təhlükəsiz bir şəkildə təmsil etmək ideyasını müzakirə etdik. Bu yanaşma bir çox tətbiqlərdə xml və digər mətn əsaslı konfiqurasiyaları əvəz etmək üçün istifadə edilə bilər. Nümunəmiz Scala-da tətbiq olunsa da, onu digər tərtib edilə bilən dillərə də tərcümə etmək olar (Kotlin, C#, Swift və s.). Bu yanaşmanı yeni bir layihədə sınamaq və uyğun gəlmədiyi təqdirdə köhnə üsula keçmək olar.

Əlbəttə ki, tərtib edilə bilən konfiqurasiya yüksək keyfiyyətli inkişaf prosesi tələb edir. Bunun müqabilində o, eyni dərəcədə yüksək keyfiyyətli möhkəm konfiqurasiya təmin etməyi vəd edir.

Bu yanaşma müxtəlif yollarla genişləndirilə bilər:

  1. Konfiqurasiyanın təsdiqlənməsini həyata keçirmək üçün makrolardan istifadə edilə bilər və hər hansı bir iş məntiqi məhdudiyyətləri uğursuz olarsa, tərtib zamanı uğursuz ola bilər.
  2. Konfiqurasiyanı domen üçün istifadəçi dostu bir şəkildə təmsil etmək üçün DSL tətbiq edilə bilər.
  3. Avtomatik konfiqurasiya tənzimləmələri ilə dinamik resursların idarə edilməsi. Məsələn, biz klaster qovşaqlarının sayını tənzimləyərkən (1) qovşaqların bir qədər dəyişdirilmiş konfiqurasiya əldə etməsini istəyə bilərik; (2) yeni qovşaqlar haqqında məlumat almaq üçün klaster meneceri.

təşəkkür

Andrey Saksonova, Pavel Popova, Anton Nehaev-ə bu yazının layihəsi ilə bağlı ruhlandırıcı rəy bildirdikləri üçün təşəkkür etmək istərdim ki, bu da mənə aydınlıq gətirməyə kömək etdi.

Mənbə: www.habr.com