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.
(
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
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
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 EndPoint
digə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ə haldaF[A]
sadəcə funksiya ola bilər() => A
. Bu yazıda istifadə edəcəyikcats.IO
.)Reader[A,B]
- funksiya ilə az-çox sinonimdirA => B
cats.Resource
- əldə edilə və buraxıla bilən resursTimer
— taymer (bir müddət yuxuya getməyə və vaxt intervallarını ölçməyə imkan verir)ContextShift
- analoqExecutionContext
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ürMonad
/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.
и
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:
- Ə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ərMap[NodeId, NodeAddress]
. - 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. - 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ənmy-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:
- Bütün xidmətlərin bir node üzərində yerləşdirilməsi.
- Fərqli qovşaqlarda əks-səda xidmətini və echo müştərisini yerləşdirmək.
Üçün konfiqurasiya
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
İ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.
Ü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:
-
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.
-
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.
-
Konfiqurasiyada kiçik dəyişikliklər etmək çətinləşir.
-
Ə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:
- Hədəf maşındakı mətn faylı.
- Mərkəzləşdirilmiş açar-dəyər mağazası (
etcd
/zookeeper
). - Prosesi yenidən başlatmadan yenidən konfiqurasiya edilə/yenidən işə salına bilən proses komponentləri.
- 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. Config
və 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
İ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:
- Statik konfiqurasiya yoxlanışı. Buna əmin olmağa imkan verir
konfiqurasiya düzgündür. - 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). - 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- İ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:
- 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.
- 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.
- 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.
- 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.
- 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:
- Ə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). - 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. - 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:
- Kompilyasiya vaxtı yoxlamalarını yerinə yetirmək üçün makrolardan istifadə edə bilərsiniz.
- Siz konfiqurasiyanı son istifadəçilər üçün əlçatan olan şəkildə təqdim etmək üçün DSL tətbiq edə bilərsiniz.
- 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