Tərtib edilmiş Paylanmış Sistem Konfiqurasiyası

Mən sizə paylanmış sistemin konfiqurasiyası ilə işləmək üçün maraqlı bir mexanizm demək istərdim. Konfiqurasiya təhlükəsiz növlərdən istifadə etməklə birbaşa tərtib edilmiş dildə (Scala) təmsil olunur. Bu yazı belə bir konfiqurasiyanın nümunəsini təqdim edir və tərtib edilmiş konfiqurasiyanın ümumi inkişaf prosesinə tətbiqinin müxtəlif aspektlərini müzakirə edir.

Tərtib edilmiş Paylanmış Sistem Konfiqurasiyası

(ingilis)

Giriş

Etibarlı paylanmış sistemin qurulması bütün qovşaqların digər qovşaqlarla sinxronlaşdırılmış düzgün konfiqurasiyadan istifadə etməsi deməkdir. DevOps texnologiyaları (terraform, ansible və ya bu kimi bir şey) adətən avtomatik olaraq konfiqurasiya faylları yaratmaq üçün istifadə olunur (çox vaxt hər bir node üçün xüsusi). Biz həmçinin əmin olmaq istərdik ki, bütün rabitə qovşaqları eyni protokollardan (eyni versiya daxil olmaqla) istifadə edir. Əks təqdirdə, paylanmış sistemimizə uyğunsuzluq qurulacaq. JVM dünyasında bu tələbin bir nəticəsi odur ki, protokol mesajlarını ehtiva edən kitabxananın eyni versiyası hər yerdə istifadə edilməlidir.

Paylanmış sistemin sınaqdan keçirilməsi haqqında nə demək olar? Əlbəttə ki, biz inteqrasiya testinə keçməzdən əvvəl bütün komponentlərin vahid testlərinin olduğunu güman edirik. (Test nəticələrini icra müddətinə ekstrapolyasiya etmək üçün biz sınaq mərhələsində və iş vaxtında eyni kitabxana dəstini də təqdim etməliyik.)

İnteqrasiya testləri ilə işləyərkən, bütün qovşaqlarda hər yerdə eyni sinif yolundan istifadə etmək çox vaxt daha asandır. Etməli olduğumuz tək şey, eyni sinif yolunun iş vaxtında istifadə olunmasını təmin etməkdir. (Fərqli sinif yolları ilə müxtəlif qovşaqları işə salmaq tamamilə mümkün olsa da, bu, ümumi konfiqurasiyaya mürəkkəblik və yerləşdirmə və inteqrasiya testləri ilə bağlı çətinliklər əlavə edir.) Bu yazının məqsədləri üçün biz bütün qovşaqların eyni sinif yolundan istifadə edəcəyini güman edirik.

Konfiqurasiya tətbiq ilə birlikdə inkişaf edir. Proqram təkamülünün müxtəlif mərhələlərini müəyyən etmək üçün versiyalardan istifadə edirik. Konfiqurasiyaların müxtəlif versiyalarını da müəyyən etmək məntiqli görünür. Və konfiqurasiyanın özünü versiyaya nəzarət sisteminə yerləşdirin. İstehsalda yalnız bir konfiqurasiya varsa, o zaman sadəcə versiya nömrəsindən istifadə edə bilərik. Çox istehsal nümunələrindən istifadə etsək, bir neçə ehtiyacımız olacaq
konfiqurasiya filialları və versiyaya əlavə olaraq əlavə etiket (məsələn, filialın adı). Bu yolla biz dəqiq konfiqurasiyanı aydın şəkildə müəyyən edə bilərik. Hər bir konfiqurasiya identifikatoru unikal şəkildə paylanmış qovşaqların, portların, xarici resursların və kitabxana versiyalarının xüsusi birləşməsinə uyğun gəlir. Bu yazının məqsədləri üçün biz fərz edəcəyik ki, yalnız bir filial var və biz nöqtə ilə ayrılmış üç rəqəmdən istifadə edərək konfiqurasiyanı adi şəkildə müəyyən edə bilərik (1.2.3).

Müasir mühitlərdə konfiqurasiya faylları nadir hallarda əl ilə yaradılır. Daha tez-tez onlar yerləşdirmə zamanı yaradılır və artıq toxunulmur (belə ki heç nəyi pozma). Təbii sual yaranır: niyə konfiqurasiyanı saxlamaq üçün hələ də mətn formatından istifadə edirik? Münasib bir alternativ, konfiqurasiya üçün adi koddan istifadə etmək və tərtib vaxtı yoxlamalarından faydalanmaq qabiliyyəti kimi görünür.

Bu yazıda biz tərtib edilmiş artefakt daxilində konfiqurasiyanı təmsil etmək ideyasını araşdıracağıq.

Tərtib edilmiş konfiqurasiya

Bu bölmə statik tərtib edilmiş konfiqurasiya nümunəsini təqdim edir. İki sadə xidmət həyata keçirilir - əks-səda xidməti və əks-səda xidməti müştərisi. Bu iki xidmət əsasında iki sistem variantı yığılır. Bir seçimdə hər iki xidmət eyni qovşaqda, digər variantda isə müxtəlif qovşaqlarda yerləşir.

Tipik olaraq paylanmış sistem bir neçə qovşaqdan ibarətdir. Bəzi növ dəyərlərdən istifadə edərək qovşaqları müəyyən edə bilərsiniz NodeId:

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

və ya

case class NodeId(hostName: String)

ya da hətta

object Singleton
type NodeId = Singleton.type

Qovşaqlar müxtəlif rolları yerinə yetirir, xidmətlər işlədir və onlar arasında TCP/HTTP əlaqələri yaradıla bilər.

TCP bağlantısını təsvir etmək üçün bizə ən azı bir port nömrəsi lazımdır. Həm müştərinin, həm də serverin eyni protokoldan istifadə etməsini təmin etmək üçün həmin portda dəstəklənən protokolu əks etdirmək istərdik. Aşağıdakı sinifdən istifadə edərək əlaqəni təsvir edəcəyik:

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

hara Port - sadəcə tam ədəd Int məqbul dəyərlər diapazonunu göstərən:

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

Təmizlənmiş növlər

Kitabxanaya baxın nəfis и mənim hesabat. Bir sözlə, kitabxana tərtib zamanı yoxlanılan növlərə məhdudiyyətlər əlavə etməyə imkan verir. Bu halda etibarlı port nömrəsi dəyərləri 16 bitlik tam ədədlərdir. Tərtib edilmiş konfiqurasiya üçün təmizlənmiş kitabxanadan istifadə məcburi deyil, lakin bu, tərtibçinin konfiqurasiyanı yoxlamaq qabiliyyətini yaxşılaşdırır.

HTTP (REST) ​​protokolları üçün port nömrəsinə əlavə olaraq xidmətə gedən yola da ehtiyacımız 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övləri

Kompilyasiya zamanı protokolu müəyyən etmək üçün biz sinif daxilində istifadə edilməyən tip parametrindən istifadə edirik. Bu qərar icra zamanı protokol nümunəsindən istifadə etməməyimizlə əlaqədardır, lakin biz tərtibçinin protokol uyğunluğunu yoxlamasını istəyirik. Protokolu qeyd etməklə, uyğun olmayan xidməti asılılıq kimi ötürə bilməyəcəyik.

Ümumi protokollardan biri Json serializasiyası ilə REST API-dir:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

hara RequestMessage - sorğu növü, ResponseMessage - cavab növü.
Əlbəttə ki, biz tələb etdiyimiz təsvirin dəqiqliyini təmin edən digər protokol təsvirlərindən istifadə edə bilərik.

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

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Burada sorğu url-ə əlavə edilmiş sətirdir və cavab HTTP cavabının gövdəsində qaytarılmış sətirdir.

Xidmət konfiqurasiyası xidmət adı, portlar və asılılıqlarla təsvir olunur. Bu elementlər Scala-da bir neçə yolla təmsil oluna bilər (məsələn, HList-s, 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ə modullardan istifadə edəcəyik trait'ov. (Tort Nümunəsi bu yanaşmanın tələb olunan elementi deyil. Bu, sadəcə mümkün tətbiqlərdən biridir.)

Xidmətlər arasındakı asılılıqlar portları qaytaran üsullar kimi təqdim edilə bilər EndPointdigər qovşaqların:

  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əti yaratmaq üçün sizə lazım olan tək şey port nömrəsi və portun əks-səda protokolunu dəstəklədiyinin göstəricisidir. Biz konkret port göstərməyə bilərik, çünki... əlamətlər həyata keçirmədən metodları elan etməyə imkan verir (mücərrəd üsullar). Bu halda, konkret konfiqurasiya yaratarkən, kompilyator bizdən mücərrəd metodun həyata keçirilməsini və port nömrəsini təqdim etməyi tələb edəcəkdir. Biz metodu tətbiq etdiyimiz üçün xüsusi konfiqurasiya yaratarkən başqa port göstərməyə bilərik. Varsayılan dəyər istifadə olunacaq.

Müştəri konfiqurasiyasında biz echo xidmətindən asılılığı elan edirik:

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

Asılılıq ixrac edilən xidmətlə eyni tipdədir echoService. Xüsusilə, echo müştərisində biz eyni protokolu tələb edirik. Buna görə də, iki xidməti birləşdirərkən hər şeyin düzgün işləyəcəyinə əmin ola bilərik.

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

Xidməti başlamaq və dayandırmaq üçün funksiya tələb olunur. (Xidməti dayandırmaq imkanı sınaq üçün vacibdir.) Yenə belə bir funksiyanı həyata keçirmək üçün bir neçə variant var (məsələn, konfiqurasiya növünə əsaslanan tip siniflərindən istifadə edə bilərik). Bu yazının məqsədləri üçün biz tort nümunəsindən istifadə edəcəyik. Biz bir sinifdən istifadə edərək xidməti təmsil edəcəyik cats.Resource, çünki Bu sinif artıq problemlər zamanı resursların sərbəst buraxılmasına zəmanət vermək üçün vasitələr təqdim edir. Resurs əldə etmək üçün konfiqurasiya və hazır iş vaxtı kontekstini təmin etməliyik. Xidmətin işə salınması 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 üçün konfiqurasiya növü
  • AddressResolver — digər qovşaqların ünvanlarını öyrənməyə imkan verən iş vaxtı obyekti (aşağıya bax)

və kitabxanadan digər növlər cats:

  • F[_] — təsir növü (ən sadə halda F[A] sadəcə funksiya ola bilər () => A. Bu yazıda istifadə edəcəyik cats.IO.)
  • Reader[A,B] - funksiya ilə az-çox sinonimdir A => B
  • cats.Resource - əldə edilə və buraxıla bilən resurs
  • Timer — taymer (bir müddət yuxuya getməyə və vaxt intervallarını ölçməyə imkan verir)
  • ContextShift - analoq ExecutionContext
  • Applicative — fərdi effektləri birləşdirməyə imkan verən effekt tipi sinfi (demək olar ki, monad). Daha mürəkkəb tətbiqlərdə istifadə etmək daha yaxşı görünür Monad/ConcurrentEffect.

Bu funksiya imzasından 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](()))
  }

(Santimetr. mənbə, digər xidmətlərin həyata keçirildiyi - ə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şə sala bilən bir obyektdir (resurslar zəncirinin işə salınması Cake Pattern ilə təmin edilir):

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

Nəzərə alın ki, biz bu node üçün tələb olunan konfiqurasiyanın dəqiq növünü qeyd edirik. Müəyyən bir xidmətin tələb etdiyi konfiqurasiya növlərindən birini təyin etməyi unutsaq, tərtib etmə xətası olacaq. Həmçinin, bütün lazımi məlumatlarla uyğun tipli bəzi obyektləri təqdim etməsək, qovşağı başlada bilməyəcəyik.

Host adının həlli

Uzaq hosta qoşulmaq üçün bizə real IP ünvan lazımdır. Mümkündür ki, ünvan konfiqurasiyanın qalan hissəsindən daha gec məlum olacaq. Beləliklə, bizə node ID-ni ünvana uyğunlaşdıran funksiya lazımdır:

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

Bu funksiyanı həyata keçirməyin bir neçə yolu var:

  1. Əgər ünvanlar yerləşdirmədən əvvəl bizə məlum olarsa, biz Scala kodunu yarada bilərik
    ünvanları seçin və sonra quruluşu işə salın. Bu, testləri tərtib edəcək və həyata keçirəcək.
    Bu halda, funksiya statik olaraq bilinəcək və kodda xəritəçəkmə kimi təqdim edilə bilər Map[NodeId, NodeAddress].
  2. Bəzi hallarda, faktiki ünvan yalnız node işə salındıqdan sonra məlum olur.
    Bu halda biz digər qovşaqlardan əvvəl işləyən “kəşf xidməti” həyata keçirə bilərik və bütün qovşaqlar bu xidmətdə qeydiyyatdan keçərək digər qovşaqların ünvanlarını tələb edəcək.
  3. Dəyişdirə bilsək /etc/hosts, sonra əvvəlcədən təyin edilmiş host adlarından istifadə edə bilərsiniz (məsələn my-project-main-node и echo-backend) və sadəcə bu adları əlaqələndirin
    yerləşdirmə zamanı IP ünvanları ilə.

Bu yazıda bu halları daha ətraflı nəzərdən keçirməyəcəyik. Bizim üçün
bir oyuncaq nümunəsində, bütün qovşaqlar eyni IP ünvanına sahib olacaq - 127.0.0.1.

Sonra, paylanmış sistem üçün iki variantı nəzərdən keçiririk:

  1. Bütün xidmətlərin bir node üzərində yerləşdirilməsi.
  2. Fərqli qovşaqlarda əks-səda xidmətini və echo müştərisini yerləşdirmək.

Üçün konfiqurasiya bir düyün:

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

Obyekt həm müştərinin, həm də serverin konfiqurasiyasını həyata keçirir. Yaşamaq üçün vaxt konfiqurasiyası da intervaldan sonra istifadə olunur lifetime proqramı dayandırın. (Ctrl-C də işləyir və bütün resursları düzgün şəkildə azad edir.)

Eyni konfiqurasiya və həyata keçirmə xüsusiyyətlərindən ibarət bir sistem yaratmaq üçün istifadə edilə bilər iki ayrı qovşaq:

İki node 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"
  }

Vacibdir! Xidmətlərin necə əlaqəli olduğuna diqqət yetirin. Biz bir node tərəfindən həyata keçirilən xidməti digər qovşağın asılılıq metodunun həyata keçirilməsi kimi təyin edirik. Asılılıq tipi kompilyator tərəfindən yoxlanılır, çünki protokol tipini ehtiva edir. İşlədikdə, asılılıq düzgün hədəf node ID-ni ehtiva edəcəkdir. Bu sxem sayəsində biz port nömrəsini tam olaraq bir dəfə təyin edirik və həmişə düzgün porta müraciət etməyə zəmanət veririk.

İki sistem qovşağının həyata keçirilməsi

Bu konfiqurasiya üçün biz dəyişmədən eyni xidmət tətbiqlərindən istifadə edirik. Yeganə fərq ondadır ki, indi müxtəlif xidmətlər dəstini həyata keçirən iki obyektimiz var:

  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 serveri həyata keçirir və yalnız server konfiqurasiyasına ehtiyac duyur. İkinci node müştərini həyata keçirir və konfiqurasiyanın fərqli bir hissəsindən istifadə edir. Həm də hər iki qovşaq ömür boyu idarəetməyə ehtiyac duyur. Server node dayandırılana qədər qeyri-müəyyən müddətə işləyir SIGTERM'om və müştəri qovşağı bir müddət sonra dayandırılır. Santimetr. başlatma proqramı.

Ümumi inkişaf prosesi

Bu konfiqurasiya yanaşmasının ümumi inkişaf prosesinə necə təsir etdiyini görək.

Konfiqurasiya kodun qalan hissəsi ilə birlikdə tərtib ediləcək və artefakt (.jar) yaradılacaq. Konfiqurasiyanı ayrıca bir artefaktda yerləşdirmək məntiqli görünür. Bunun səbəbi eyni koda əsaslanan bir neçə konfiqurasiyaya sahib ola bilməmizdir. Yenə də müxtəlif konfiqurasiya filiallarına uyğun artefaktlar yaratmaq mümkündür. Kitabxanaların xüsusi versiyalarından asılılıqlar konfiqurasiya ilə birlikdə saxlanılır və biz konfiqurasiyanın həmin versiyasını yerləşdirməyə qərar verdiyimiz zaman bu versiyalar həmişəlik saxlanılır.

İstənilən konfiqurasiya dəyişikliyi kod dəyişikliyinə çevrilir. Və buna görə də, hər biri
Dəyişiklik normal keyfiyyət təminatı prosesi ilə əhatə olunacaq:

Səhv izləyicisində bilet -> PR -> nəzərdən keçirin -> müvafiq filiallarla birləşdirin ->
inteqrasiya -> yerləşdirmə

Tərtib edilmiş konfiqurasiyanın həyata keçirilməsinin əsas nəticələri:

  1. Konfiqurasiya paylanmış sistemin bütün qovşaqlarında ardıcıl olacaq. Bütün qovşaqların eyni konfiqurasiyanı bir mənbədən alması səbəbindən.

  2. Düyünlərdən yalnız birində konfiqurasiyanı dəyişdirmək problemlidir. Buna görə də, "konfiqurasiya sürüşməsi" mümkün deyil.

  3. Konfiqurasiyada kiçik dəyişikliklər etmək çətinləşir.

  4. Əksər konfiqurasiya dəyişiklikləri ümumi inkişaf prosesinin bir hissəsi kimi baş verəcək və nəzərdən keçirilməlidir.

İstehsal konfiqurasiyasını saxlamaq üçün mənə ayrıca depo lazımdırmı? Bu konfiqurasiyada girişi məhdudlaşdırmaq istədiyimiz parollar və digər həssas məlumatlar ola bilər. Buna əsaslanaraq, son konfiqurasiyanı ayrıca depoda saxlamağın mənası var. Siz konfiqurasiyanı iki hissəyə ayıra bilərsiniz - biri ictimai əlçatan olan konfiqurasiya parametrlərini, digəri isə məhdudlaşdırılmış parametrləri ehtiva edir. Bu, əksər tərtibatçılara ümumi parametrlərə daxil olmaq imkanı verəcək. Bu ayırma standart dəyərləri ehtiva edən ara əlamətlərdən istifadə etməklə əldə etmək asandır.

Mümkün varyasyonlar

Tərtib edilmiş konfiqurasiyanı bəzi ümumi alternativlərlə müqayisə etməyə çalışaq:

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

Mətn faylları kiçik dəyişikliklər baxımından əhəmiyyətli rahatlıq təmin edir. Sistem administratoru uzaq node daxil ola, müvafiq fayllara dəyişiklik edə və xidməti yenidən başlada bilər. Böyük sistemlər üçün isə belə çeviklik arzuolunan olmaya bilər. Edilən dəyişikliklər digər sistemlərdə heç bir iz buraxmır. Dəyişiklikləri heç kim nəzərdən keçirmir. Dəyişiklikləri konkret kimin və hansı səbəbdən etdiyini müəyyən etmək çətindir. Dəyişikliklər sınaqdan keçirilmir. Sistem paylanırsa, administrator digər qovşaqlarda müvafiq dəyişikliyi etməyi unuda bilər.

(Onu da qeyd etmək lazımdır ki, tərtib edilmiş konfiqurasiyadan istifadə gələcəkdə mətn fayllarından istifadə imkanını bağlamır. Çıxış kimi eyni tip istehsal edən analizator və validator əlavə etmək kifayətdir. Configvə mətn fayllarından istifadə edə bilərsiniz. Dərhal belə çıxır ki, tərtib edilmiş konfiqurasiyaya malik sistemin mürəkkəbliyi mətn fayllarından istifadə edən sistemin mürəkkəbliyindən bir qədər azdır, çünki mətn faylları əlavə kod tələb edir.)

Mərkəzləşdirilmiş açar-dəyər mağazası paylanmış tətbiqin meta parametrlərinin paylanması üçün yaxşı mexanizmdir. Konfiqurasiya parametrlərinin nə olduğuna və yalnız məlumatların nə olduğuna qərar verməliyik. Bir funksiyamız olsun C => A => B, və parametrlər C nadir hallarda dəyişir və məlumatlar A - tez-tez. Bu vəziyyətdə bunu deyə bilərik C - konfiqurasiya parametrləri və A - verilənlər. Görünür, konfiqurasiya parametrləri verilənlərdən fərqlənir ki, onlar ümumiyyətlə verilənlərdən daha az dəyişirlər. Həmçinin, məlumatlar adətən bir mənbədən (istifadəçidən), konfiqurasiya parametrləri isə digərindən (sistem administratorundan) gəlir.

Nadir hallarda dəyişən parametrləri proqramı yenidən başlatmadan yeniləmək lazımdırsa, bu, çox vaxt proqramın çətinləşməsinə səbəb ola bilər, çünki biz bir şəkildə parametrləri çatdırmalı, saxlamalı, təhlil etməli və yoxlamalı və səhv dəyərləri emal etməli olacağıq. Buna görə də, proqramın mürəkkəbliyini azaltmaq nöqteyi-nəzərindən proqramın işləməsi zamanı dəyişə bilən parametrlərin sayını azaltmaq (və ya belə parametrləri ümumiyyətlə dəstəkləməmək) məna kəsb edir.

Bu yazının məqsədləri üçün biz statik və dinamik parametrləri fərqləndirəcəyik. Əgər xidmətin məntiqi proqramın işləməsi zamanı parametrlərin dəyişdirilməsini tələb edirsə, onda belə parametrləri dinamik adlandıracağıq. Əks halda seçimlər statikdir və tərtib edilmiş konfiqurasiyadan istifadə etməklə konfiqurasiya edilə bilər. Dinamik yenidən konfiqurasiya üçün əməliyyat sistemi proseslərinin yenidən işə salınmasına bənzər proqramın hissələrini yeni parametrlərlə yenidən başlatmaq üçün mexanizmə ehtiyacımız ola bilər. (Bizim fikrimizcə, real vaxt rejimində yenidən konfiqurasiyadan qaçmaq məsləhətdir, çünki bu, sistemin mürəkkəbliyini artırır. Mümkünsə, prosesləri yenidən başlatmaq üçün standart ƏS imkanlarından istifadə etmək daha yaxşıdır.)

İnsanları dinamik yenidən konfiqurasiya haqqında düşünməyə vadar edən statik konfiqurasiyadan istifadənin vacib aspektlərindən biri konfiqurasiya yeniləməsindən sonra sistemin yenidən işə salınması üçün tələb olunan vaxtdır (dayanma vaxtı). Əslində, statik konfiqurasiyada dəyişiklik etməli olsaq, yeni dəyərlərin qüvvəyə minməsi üçün sistemi yenidən başlatmalı olacağıq. Fərqli sistemlər üçün kəsilmə problemi ciddilik dərəcəsinə görə dəyişir. Bəzi hallarda, yükün minimal olduğu bir vaxtda yenidən yükləməni planlaşdıra bilərsiniz. Davamlı xidmət göstərmək lazımdırsa, həyata keçirə bilərsiniz AWS ELB bağlantısının boşaldılması. Eyni zamanda, sistemi yenidən yükləməli olduğumuz zaman, bu sistemin paralel bir nümunəsini işə salırıq, balanslaşdırıcını ona keçirik və köhnə əlaqələrin tamamlanmasını gözləyirik. Bütün köhnə əlaqələr kəsildikdən sonra sistemin köhnə nümunəsini bağlayırıq.

İndi konfiqurasiyanın artefaktın içərisində və ya xaricində saxlanması məsələsini nəzərdən keçirək. Əgər konfiqurasiyanı artefaktın içərisində saxlayırıqsa, onda heç olmasa artefaktın yığılması zamanı konfiqurasiyanın düzgünlüyünü yoxlamaq imkanımız var idi. Konfiqurasiya idarə olunan artefaktdan kənardadırsa, bu fayla kimin və nə üçün dəyişikliklər etdiyini izləmək çətindir. Nə qədər vacibdir? Fikrimizcə, bir çox istehsal sistemləri üçün sabit və yüksək keyfiyyətli konfiqurasiya vacibdir.

Artefaktın versiyası onun nə vaxt yaradıldığını, hansı dəyərləri ehtiva etdiyini, hansı funksiyaların aktivləşdirildiyini/deaktiv edildiyini və konfiqurasiyada hər hansı dəyişikliyə kimin cavabdeh olduğunu müəyyən etməyə imkan verir. Əlbəttə ki, konfiqurasiyanı artefakt daxilində saxlamaq müəyyən səy tələb edir, ona görə də məlumatlı qərar qəbul etməlisiniz.

Pros və Cons

Təklif olunan texnologiyanın müsbət və mənfi cəhətləri üzərində dayanmaq istərdim.

Üstünlükləri

Aşağıda tərtib edilmiş paylanmış sistem konfiqurasiyasının əsas xüsusiyyətlərinin siyahısı verilmişdir:

  1. Statik konfiqurasiya yoxlanışı. Buna əmin olmağa imkan verir
    konfiqurasiya düzgündür.
  2. Zəngin konfiqurasiya dili. Tipik olaraq, digər konfiqurasiya üsulları ən çox sətir dəyişənlərinin dəyişdirilməsi ilə məhdudlaşır. Scala istifadə edərkən, konfiqurasiyanızı yaxşılaşdırmaq üçün geniş çeşidli dil xüsusiyyətləri mövcuddur. Məsələn, istifadə edə bilərik
    parametrləri qruplaşdırmaq üçün obyektlərdən istifadə edərək defolt dəyərlər üçün əlamətlər, biz əlavə əhatə dairəsində yalnız bir dəfə (QURU) elan edilmiş dəyərlərə müraciət edə bilərik. Siz birbaşa konfiqurasiya daxilində istənilən sinfi işə sala bilərsiniz (Seq, Map, xüsusi siniflər).
  3. DSL. Scala DSL yaratmağı asanlaşdıran bir sıra dil xüsusiyyətlərinə malikdir. Bu xüsusiyyətlərdən yararlanmaq və konfiqurasiyanın ən azı domen ekspertləri tərəfindən oxuna bilməsi üçün hədəf istifadəçilər qrupu üçün daha əlverişli olan konfiqurasiya dilini həyata keçirmək mümkündür. Mütəxəssislər, məsələn, konfiqurasiyanın nəzərdən keçirilməsi prosesində iştirak edə bilərlər.
  4. Düyünlər arasında bütövlük və sinxronluq. Bütün paylanmış sistemin konfiqurasiyasının bir nöqtədə saxlanmasının üstünlüklərindən biri odur ki, bütün dəyərlər tam olaraq bir dəfə elan edilir və sonra lazım olduqda yenidən istifadə olunur. Portları elan etmək üçün fantom tiplərdən istifadə qovşaqların bütün düzgün sistem konfiqurasiyalarında uyğun protokollardan istifadə etməsini təmin edir. Qovşaqlar arasında açıq məcburi asılılıqların olması bütün xidmətlərin qoşulmasını təmin edir.
  5. Yüksək keyfiyyətli dəyişikliklər. Ümumi inkişaf prosesindən istifadə edərək konfiqurasiyada dəyişikliklərin edilməsi konfiqurasiya üçün də yüksək keyfiyyət standartlarına nail olmağa imkan verir.
  6. Eyni vaxtda konfiqurasiya yeniləməsi. Konfiqurasiya dəyişikliklərindən sonra avtomatik sistemin yerləşdirilməsi bütün qovşaqların yenilənməsini təmin edir.
  7. Tətbiqin sadələşdirilməsi. Tətbiqin təhlili, konfiqurasiyanın yoxlanılması və ya yanlış dəyərlərin idarə edilməsinə ehtiyac yoxdur. Bu, tətbiqin mürəkkəbliyini azaldır. (Nümunəmizdə müşahidə edilən bəzi konfiqurasiya mürəkkəbliyi tərtib edilmiş konfiqurasiyanın atributu deyil, yalnız daha böyük tip təhlükəsizliyi təmin etmək istəyi ilə idarə olunan şüurlu bir qərardır.) Adi konfiqurasiyaya qayıtmaq olduqca asandır - sadəcə çatışmayanları həyata keçirin. hissələri. Buna görə, məsələn, lazımsız hissələrin həyata keçirilməsini həqiqətən lazım olan vaxta qədər təxirə salaraq tərtib edilmiş bir konfiqurasiya ilə başlaya bilərsiniz.
  8. Təsdiqlənmiş konfiqurasiya. Konfiqurasiya dəyişiklikləri hər hansı digər dəyişikliklərin adi taleyini izlədiyi üçün əldə etdiyimiz çıxış unikal versiyaya malik artefaktdır. Bu, məsələn, zəruri hallarda konfiqurasiyanın əvvəlki versiyasına qayıtmağa imkan verir. Hətta bir il əvvəlki konfiqurasiyadan istifadə edə bilərik və sistem eyni şəkildə işləyəcək. Sabit konfiqurasiya paylanmış sistemin proqnozlaşdırıla bilənliyini və etibarlılığını artırır. Konfiqurasiya tərtib mərhələsində sabitləndiyi üçün istehsalda onu saxtalaşdırmaq olduqca çətindir.
  9. Modulluq. Təklif olunan çərçivə moduldur və modullar müxtəlif sistemlər yaratmaq üçün müxtəlif yollarla birləşdirilə bilər. Xüsusilə, sistemi bir təcəssümdə bir qovşaqda, digərində isə çoxlu qovşaqda işləmək üçün konfiqurasiya edə bilərsiniz. Sistemin istehsal nümunələri üçün bir neçə konfiqurasiya yarada bilərsiniz.
  10. Test. Fərdi xidmətləri saxta obyektlərlə əvəz etməklə, sistemin sınaq üçün əlverişli olan bir neçə versiyasını əldə edə bilərsiniz.
  11. İnteqrasiya testi. Bütün paylanmış sistem üçün vahid konfiqurasiyaya malik olmaq inteqrasiya testinin bir hissəsi kimi bütün komponentləri idarə olunan mühitdə işə salmağa imkan verir. Məsələn, bəzi qovşaqların əlçatan olduğu bir vəziyyəti təqlid etmək asandır.

Mənfi cəhətlər və məhdudiyyətlər

Tərtib edilmiş konfiqurasiya digər konfiqurasiya yanaşmalarından fərqlənir və bəzi proqramlar üçün uyğun olmaya bilər. Aşağıda bəzi çatışmazlıqlar var:

  1. Statik konfiqurasiya. Bəzən bütün qoruyucu mexanizmləri keçərək istehsalda konfiqurasiyanı tez bir zamanda düzəltmək lazımdır. Bu yanaşma ilə daha çətin ola bilər. Ən azı, kompilyasiya və avtomatik yerləşdirmə hələ də tələb olunacaq. Bu, həm yanaşmanın faydalı xüsusiyyəti, həm də bəzi hallarda çatışmazlıqdır.
  2. Konfiqurasiya generasiyası. Konfiqurasiya faylı avtomatik alət tərəfindən yaradıldığı halda, qurma skriptini inteqrasiya etmək üçün əlavə səylər tələb oluna bilər.
  3. Alətlər. Hazırda konfiqurasiya ilə işləmək üçün nəzərdə tutulmuş kommunal proqramlar və texnikalar mətn fayllarına əsaslanır. Bütün bu cür yardım proqramları/texnikaları tərtib edilmiş konfiqurasiyada mövcud olmayacaq.
  4. Münasibətlərdə dəyişiklik tələb olunur. Tərtibatçılar və DevOps mətn fayllarına öyrəşiblər. Konfiqurasiyanın tərtib edilməsi ideyası bir qədər gözlənilməz və qeyri-adi ola bilər və imtinaya səbəb ola bilər.
  5. Yüksək keyfiyyətli inkişaf prosesi tələb olunur. Tərtib edilmiş konfiqurasiyadan rahat istifadə etmək üçün proqramın qurulması və yerləşdirilməsi prosesinin tam avtomatlaşdırılması (CI/CD) lazımdır. Əks halda olduqca əlverişsiz olacaq.

Nəzərə alınan nümunənin tərtib edilmiş konfiqurasiya ideyası ilə əlaqəli olmayan bir sıra məhdudiyyətləri üzərində də dayanaq:

  1. Əgər biz node tərəfindən istifadə edilməyən lazımsız konfiqurasiya məlumatını təqdim etsək, onda tərtibçi bizə çatışmayan tətbiqi aşkar etməyə kömək etməyəcək. Bu problem Tort Nümunəsindən imtina etməklə və daha sərt növlərdən istifadə etməklə həll edilə bilər, məsələn, HList və ya konfiqurasiyanı təmsil etmək üçün cəbri məlumat növləri (hal sinifləri).
  2. Konfiqurasiya faylında konfiqurasiyanın özü ilə əlaqəli olmayan sətirlər var: (package, import,obyekt bəyannamələri; override def's default dəyərləri olan parametrlər üçün). Öz DSL-nizi tətbiq etsəniz, bunun qarşısını qismən almaq olar. Bundan əlavə, digər konfiqurasiya növləri (məsələn, XML) də fayl strukturuna müəyyən məhdudiyyətlər qoyur.
  3. Bu yazının məqsədləri üçün biz oxşar qovşaqların çoxluğunun dinamik yenidən konfiqurasiyasını nəzərdən keçirmirik.

Nəticə

Bu yazıda biz Scala tipli sistemin qabaqcıl imkanlarından istifadə edərək mənbə kodunda konfiqurasiyanı təmsil etmək ideyasını araşdırdıq. Bu yanaşma müxtəlif proqramlarda xml və ya mətn fayllarına əsaslanan ənənəvi konfiqurasiya üsullarını əvəz etmək üçün istifadə edilə bilər. Nümunəmiz Scala-da həyata keçirilsə də, eyni fikirlər digər tərtib edilmiş dillərə (Kotlin, C#, Swift, ... kimi) köçürülə bilər. Siz bu yanaşmanı aşağıdakı layihələrdən birində sınaya bilərsiniz və əgər bu işləmirsə, çatışmayan hissələri əlavə edərək mətn faylına keçin.

Təbii ki, tərtib edilmiş konfiqurasiya yüksək keyfiyyətli inkişaf prosesi tələb edir. Bunun müqabilində konfiqurasiyaların yüksək keyfiyyəti və etibarlılığı təmin edilir.

Nəzərdə tutulan yanaşma genişləndirilə bilər:

  1. Kompilyasiya vaxtı yoxlamalarını yerinə yetirmək üçün makrolardan istifadə edə bilərsiniz.
  2. Siz konfiqurasiyanı son istifadəçilər üçün əlçatan olan şəkildə təqdim etmək üçün DSL tətbiq edə bilərsiniz.
  3. Siz avtomatik konfiqurasiya tənzimlənməsi ilə dinamik resurs idarəetməsini həyata keçirə bilərsiniz. Məsələn, klasterdəki qovşaqların sayının dəyişdirilməsi tələb edir ki, (1) hər bir qovşaq bir qədər fərqli konfiqurasiya alsın; (2) klaster meneceri yeni qovşaqlar haqqında məlumat aldı.

Təşəkkürlər

Mən Andrey Saksonova, Pavel Popova və Anton Nexayevə layihənin məqaləsini konstruktiv tənqid etdiklərinə görə təşəkkür etmək istərdim.

Mənbə: www.habr.com

Добавить комментарий