Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi

Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi
Rube Goldberg qəhvə maşını

Hadisələrə əsaslanan arxitektura istifadə olunan resursların səmərəliliyini artırır, çünki onlar yalnız ehtiyac duyulduğu anda istifadə olunur. Bunu necə həyata keçirmək və işçi tətbiqləri kimi əlavə bulud obyektləri yaratmamaq üçün bir çox variant var. Və bu gün mən FaaS haqqında deyil, webhooks haqqında danışacağam. Obyekt saxlama veb-qançlarından istifadə edərək hadisələrin idarə edilməsinə dair dərslik nümunəsini göstərəcəyəm.

Obyekt saxlama və webhooks haqqında bir neçə söz. Obyektin saxlanması istənilən məlumatı buludda obyektlər şəklində saxlamağa imkan verir, HTTP/HTTPS vasitəsilə S3 və ya digər API (tətbiqdən asılı olaraq) vasitəsilə əldə edilə bilər. Webhooks ümumiyyətlə xüsusi HTTP geri çağırışlarıdır. Onlar adətən kodun anbara köçürülməsi və ya bloqda yerləşdirilən şərh kimi bir hadisə ilə tetiklenir. Hadisə baş verdikdə, mənbə saytı webhook üçün müəyyən edilmiş URL-ə HTTP sorğusu göndərir. Nəticədə siz bir saytdakı hadisələri digərində hərəkətə keçirə bilərsiniz (wiki). Mənbə saytının obyekt anbarı olduğu halda, hadisələr onun məzmununda dəyişikliklər kimi çıxış edir.

Belə avtomatlaşdırmanın istifadə oluna biləcəyi sadə hallara nümunələr:

  1. Başqa bulud anbarında bütün obyektlərin surətlərinin yaradılması. Fayllar əlavə edildikdə və ya dəyişdirildikdə nüsxələr tez yaradılmalıdır.
  2. Qrafik faylların bir sıra miniatürlərinin avtomatik yaradılması, fotoşəkillərə su nişanlarının əlavə edilməsi və digər şəkil dəyişiklikləri.
  3. Yeni sənədlərin gəlməsi haqqında bildiriş (məsələn, paylanmış mühasibat xidməti hesabatları buludlara yükləyir və maliyyə monitorinqi yeni hesabatlar haqqında bildirişlər alır, onları yoxlayır və təhlil edir).
  4. Bir az daha mürəkkəb hallar, məsələn, Kubernetes-ə sorğunun yaradılmasını əhatə edir ki, bu da lazımi konteynerlərlə pod yaradır, ona tapşırıq parametrlərini ötürür və emaldan sonra konteyneri yıxır.

Nümunə olaraq, Mail.ru Bulud Həlləri (MCS) obyekt saxlama kovasındakı dəyişikliklər webhooks istifadə edərək AWS obyekt yaddaşında sinxronlaşdırıldıqda, biz tapşırığın 1 variantını edəcəyik. Həqiqi yüklənmiş vəziyyətdə, asinxron iş veb kancaları bir növbədə qeyd etməklə təmin edilməlidir, lakin təlim tapşırığı üçün biz bu olmadan həyata keçirəcəyik.

İş planı

Qarşılıqlı əlaqə protokolu ətraflı təsvir edilmişdir MCS-də S3 webhooks üçün bələdçi. İş sxemi aşağıdakı elementləri ehtiva edir:

  • Nəşriyyat xidməti, S3 saxlama tərəfindədir və webnhook işə salındıqda HTTP sorğularını dərc edir.
  • Webhook qəbul edən server, HTTP nəşr xidmətindən gələn sorğuları dinləyir və müvafiq hərəkətləri yerinə yetirir. Server istənilən dildə yazıla bilər, bizim nümunəmizdə serveri Go-da yazacağıq.

S3 API-də webhook-ların tətbiqinin xüsusi xüsusiyyəti veb-qanca qəbul edən serverin nəşriyyat xidmətində qeydiyyatdan keçməsidir. Xüsusilə, webhook qəbul edən server nəşriyyat xidmətindən mesajlara abunəni təsdiq etməlidir (digər webhook tətbiqlərində adətən abunəliyin təsdiqi tələb olunmur).

Müvafiq olaraq, webhook qəbul edən server iki əsas əməliyyatı dəstəkləməlidir:

  • qeydiyyatı təsdiqləmək üçün nəşriyyat xidmətinin sorğusuna cavab vermək,
  • daxil olan hadisələri emal edin.

Webhook qəbuledici serverin quraşdırılması

Webhook qəbuledici serveri işə salmaq üçün sizə Linux server lazımdır. Bu məqalədə nümunə olaraq MCS-də yerləşdirdiyimiz virtual nümunədən istifadə edirik.

Lazımi proqram təminatını quraşdıraq və webhook qəbul edən serveri işə salaq.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bc dns-root-data dnsmasq-base ebtables landscape-common liblxc-common 
liblxc1 libuv1 lxcfs lxd lxd-client python3-attr python3-automat 
python3-click python3-constantly python3-hyperlink
  python3-incremental python3-pam python3-pyasn1-modules 
python3-service-identity python3-twisted python3-twisted-bin 
python3-zope.interface uidmap xdelta3
Use 'sudo apt autoremove' to remove them.
Suggested packages:
  git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui 
gitk gitweb git-cvs git-mediawiki git-svn
The following NEW packages will be installed:
  git
0 upgraded, 1 newly installed, 0 to remove and 46 not upgraded.
Need to get 3915 kB of archives.
After this operation, 32.3 MB of additional disk space will be used.
Get:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main 
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Fetched 3915 kB in 1s (5639 kB/s)
Selecting previously unselected package git.
(Reading database ... 53932 files and directories currently installed.)
Preparing to unpack .../git_1%3a2.17.1-1ubuntu0.7_amd64.deb ...
Unpacking git (1:2.17.1-1ubuntu0.7) ...
Setting up git (1:2.17.1-1ubuntu0.7) ...

Qovluğu webhook qəbuledici serverlə klonlayın:

ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Cloning into 's3-webhook'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 114 (delta 20), reused 45 (delta 18), pack-reused 66
Receiving objects: 100% (114/114), 23.77 MiB | 20.25 MiB/s, done.
Resolving deltas: 100% (49/49), done.

Serverə başlayaq:

ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80

Nəşriyyat xidmətinə abunə olun

Webhook qəbuledici serverinizi API və ya veb interfeys vasitəsilə qeydiyyatdan keçirə bilərsiniz. Sadəlik üçün biz veb-interfeys vasitəsilə qeydiyyatdan keçəcəyik:

  1. Gəlin vedrələr bölməsinə keçək nəzarət otağında.
  2. Veb qarmaqları konfiqurasiya edəcəyimiz vedrə gedin və dişli üzərinə vurun:

Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi

Webhooks sekmesine keçin və Əlavə et düyməsini basın:

Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi
Sahələri doldurun:

Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi

ID — webhookun adı.

Hadisə - hansı hadisələrin ötürülməsi. Fayllarla işləyərkən (əlavə və silmə) baş verən bütün hadisələrin ötürülməsini təyin etdik.

URL — server ünvanı qəbul edən webhook.

Filtr prefiksi/şəkilçisi yalnız adları müəyyən qaydalara uyğun gələn obyektlər üçün veb-qancalar yaratmağa imkan verən filtrdir. Məsələn, webhookun yalnız .png uzantılı faylları işə salması üçün Filtr şəkilçisi "png" yazmalısınız.

Hal-hazırda, webhook qəbuledici serverə daxil olmaq üçün yalnız 80 və 443 portları dəstəklənir.

Gəlin klikləyək Çəngəl əlavə edin və biz aşağıdakıları görəcəyik:

Mail.ru Bulud Həllərinin S3 obyekt anbarında veb-qancalara əsaslanan hadisəyə əsaslanan proqram nümunəsi
Qarmaq əlavə edildi.

Webhook qəbul edən server öz qeydlərində çəngəl qeydiyyatı prosesinin gedişatını göstərir:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] incoming HTTP request from 
95.163.216.92:42530
2020/06/15 12:01:14 Got timestamp: 2020-06-15T15:01:13+03:00 TopicArn: 
mcs5259999770|myfiles-ash|s3:ObjectCreated:*,s3:ObjectRemoved:* Token: 
E2itMqAMUVVZc51pUhFWSp13DoxezvRxkUh5P7LEuk1dEe9y URL: 
http://89.208.199.220/webhook
2020/06/15 12:01:14 Generate responce signature: 
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66d

Qeydiyyat tamamlandı. Növbəti bölmədə biz webhook qəbuledici serverin işləmə alqoritmini daha yaxından nəzərdən keçirəcəyik.

Webhook qəbul edən serverin təsviri

Bizim nümunəmizdə server Go-da yazılmışdır. Onun fəaliyyətinin əsas prinsiplərinə baxaq.

package main

// Generate hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}

// Generate hmac_sha256
func HmacSha256(message string, secret string) string {
}

// Send subscription confirmation
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Send subscription confirmation
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
    // log request
    log.Printf("[%s] incoming HTTP Ping request from %sn", req.Method, req.RemoteAddr)
    fmt.Fprintf(w, "Pongn")
}

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}

func main() {

    // get command line args
    bindPort := flag.Int("port", 80, "number between 1-65535")
    bindAddr := flag.String("address", "", "ip address in dot format")
    flag.StringVar(&actionScript, "script", "", "external script to execute")
    flag.Parse()

    http.HandleFunc("/ping", Ping)
    http.HandleFunc("/webhook", Webhook)

log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}

Əsas funksiyaları nəzərdən keçirin:

  • Ping() - URL/ping vasitəsilə cavab verən marşrut, canlılıq zondunun ən sadə tətbiqi.
  • Webhook() - əsas marşrut, URL/webhook işləyicisi:
    • nəşriyyat xidmətində qeydiyyatı təsdiqləyir (Abunəliyin Təsdiqi funksiyasına keçin),
    • daxil olan webhookları emal edir (Gorecords funksiyası).
  • HmacSha256 və HmacSha256hex funksiyaları imzanın hesablanması üçün onaltılıq nömrələr sətri kimi çıxışla HMAC-SHA256 və HMAC-SHA256 şifrələmə alqoritmlərinin tətbiqidir.
  • main əsas funksiyadır, komanda xətti parametrlərini emal edir və URL işləyicilərini qeyd edir.

Server tərəfindən qəbul edilən komanda xətti parametrləri:

  • -port serverin dinləyəcəyi portdur.
  • -ünvan - serverin dinləyəcəyi IP ünvanı.
  • -skript hər gələn çəngəl üçün çağırılan xarici proqramdır.

Bəzi funksiyalara daha yaxından nəzər salaq:

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {

    // Read body
    body, err := ioutil.ReadAll(req.Body)
    defer req.Body.Close()
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // log request
    log.Printf("[%s] incoming HTTP request from %sn", req.Method, req.RemoteAddr)
    // check if we got subscription confirmation request
    if strings.Contains(string(body), 
""Type":"SubscriptionConfirmation"") {
        SubscriptionConfirmation(w, req, body)
    } else {
        GotRecords(w, req, body)
    }

}

Bu funksiya qeydiyyatı təsdiqləmək üçün sorğunun və ya webhookun gəlib-gəlmədiyini müəyyən edir. Aşağıdakı kimi sənədləşdirmə, qeydiyyat təsdiqlənərsə, Post sorğusunda aşağıdakı Json strukturu qəbul edilir:

POST http://test.com HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation
content-type: application/json

{
    "Timestamp":"2019-12-26T19:29:12+03:00",
    "Type":"SubscriptionConfirmation",
    "Message":"You have chosen to subscribe to the topic $topic. To confirm the subscription you need to response with calculated signature",
    "TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
    "SignatureVersion":1,
    "Token":«RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA»
}

Bu sorğuya cavab vermək lazımdır:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

İmza aşağıdakı kimi hesablanır:

signature = hmac_sha256(url, hmac_sha256(TopicArn, 
hmac_sha256(Timestamp, Token)))

Veb-qanca gələrsə, Post sorğusunun strukturu belə görünür:

POST <url> HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation

{ "Records":
    [
        {
            "s3": {
                "object": {
                    "eTag":"aed563ecafb4bcc5654c597a421547b2",
                    "sequencer":1577453615,
                    "key":"some-file-to-bucket",
                    "size":100
                },
            "configurationId":"1",
            "bucket": {
                "name": "bucketA",
                "ownerIdentity": {
                    "principalId":"mcs2883541269"}
                },
                "s3SchemaVersion":"1.0"
            },
            "eventVersion":"1.0",
            "requestParameters":{
                "sourceIPAddress":"185.6.245.156"
            },
            "userIdentity": {
                "principalId":"2407013e-cbc1-415f-9102-16fb9bd6946b"
            },
            "eventName":"s3:ObjectCreated:Put",
            "awsRegion":"ru-msk",
            "eventSource":"aws:s3",
            "responseElements": {
                "x-amz-request-id":"VGJR5rtJ"
            }
        }
    ]
}

Müvafiq olaraq, sorğudan asılı olaraq, məlumatları necə emal edəcəyinizi başa düşməlisiniz. Mən göstərici olaraq girişi seçdim "Type":"SubscriptionConfirmation", çünki o, abunə təsdiqi sorğusunda mövcuddur və veb-qancada yoxdur. POST sorğusunda bu qeydin olub-olmamasına əsaslanaraq, proqramın sonrakı icrası ya funksiyaya keçir. SubscriptionConfirmation, və ya funksiyaya daxil olun GotRecords.

Abunəliyin Təsdiqi funksiyasını ətraflı nəzərdən keçirməyəcəyik, o, aşağıda göstərilən prinsiplərə uyğun olaraq həyata keçirilir. sənədləşdirmə. Bu funksiyanın mənbə koduna burada baxa bilərsiniz layihə git depoları.

GotRecords funksiyası daxil olan sorğunu təhlil edir və hər bir Record obyekti üçün xarici skripti (adı -script parametrində ötürülüb) parametrlərlə çağırır:

  • vedrə adı
  • obyekt açarı
  • fəaliyyət:
    • surəti - əgər orijinal sorğuda EventName = ObjectCreated | PutObject | PutObjectCopy
    • sil - əgər orijinal sorğuda EventName = ObjectRemoved | Obyekti Sil

Beləliklə, təsvir edildiyi kimi, bir Post sorğusu ilə bir çəngəl gələrsə yuxarıda, və -script=script.sh parametrindən sonra skript aşağıdakı kimi çağırılacaq:

script.sh  bucketA some-file-to-bucket copy

Başa düşmək lazımdır ki, bu webhook qəbuledici server tam istehsal həlli deyil, mümkün həyata keçirilməsinin sadələşdirilmiş nümunəsidir.

İş nümunəsi

Faylları MCS-dəki əsas kovadan AWS-dəki ehtiyat qutuya sinxronlaşdıraq. Əsas vedrə myfiles-ash, ehtiyat nüsxə isə myfiles-backup adlanır (AWS-də vedrə konfiqurasiyası bu məqalənin əhatə dairəsindən kənardadır). Müvafiq olaraq, fayl əsas kovaya yerləşdirildikdə onun surəti ehtiyat nüsxədə, əsasdan silindikdə isə ehtiyat nüsxədə silinməlidir.

Biz həm MCS bulud yaddaşı, həm də AWS bulud yaddaşı ilə uyğun gələn awscli yardım proqramından istifadə edərək vedrələrlə işləyəcəyik.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Reading package lists... Done
Building dependency tree
Reading state information... Done
After this operation, 34.4 MB of additional disk space will be used.
Unpacking awscli (1.14.44-1ubuntu1) ...
Setting up awscli (1.14.44-1ubuntu1) ...

S3 MCS API-yə girişi konfiqurasiya edək:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile mcs
AWS Access Key ID [None]: hdywEPtuuJTExxxxxxxxxxxxxx
AWS Secret Access Key [None]: hDz3SgxKwXoxxxxxxxxxxxxxxxxxx
Default region name [None]:
Default output format [None]:

AWS S3 API-yə girişi konfiqurasiya edək:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile aws
AWS Access Key ID [None]: AKIAJXXXXXXXXXXXX
AWS Secret Access Key [None]: dfuerphOLQwu0CreP5Z8l5fuXXXXXXXXXXXXXXXX
Default region name [None]:
Default output format [None]:

Girişləri yoxlayaq:

AWS-ə:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backup

MCS üçün, əmri işləyərkən —endpoint-url əlavə etməlisiniz:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile mcs --endpoint-url 
https://hb.bizmrg.com
2020-02-04 06:38:05 databasebackups-0cdaaa6402d4424e9676c75a720afa85
2020-05-27 10:08:33 myfiles-ash

Giriş edildi.

İndi gələn çəngəlin işlənməsi üçün skript yazaq, onu s3_backup_mcs_aws.sh adlandıraq.

#!/bin/bash
# Require aws cli
# if file added — copy it to backup bucket
# if file removed — remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"

SOURCE_BUCKET=""
SOURCE_FILE=""
ACTION=""

SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"

case ${ACTION} in
    "copy")
    ${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
    ${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
    rm ${TEMP}
    ;;

    "delete")
    ${AWSCLI_AWS} rm ${TARGET}
    ;;

    *)
    echo "Usage: 
#!/bin/bash
# Require aws cli
# if file added — copy it to backup bucket
# if file removed — remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"
SOURCE_BUCKET="${1}"
SOURCE_FILE="${2}"
ACTION="${3}"
SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"
case ${ACTION} in
"copy")
${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
rm ${TEMP}
;;
"delete")
${AWSCLI_AWS} rm ${TARGET}
;;
*)
echo "Usage: ${0} sourcebucket sourcefile copy/delete"
exit 1
;;
esac
sourcebucket sourcefile copy/delete" exit 1 ;; esac

Serverə başlayaq:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80 -
script scripts/s3_backup_mcs_aws.sh

Gəlin görək necə işləyir. vasitəsilə MCS veb interfeysi test.txt faylını myfiles-kül kovasına əlavə edin. Konsol qeydləri webhook serverinə sorğunun edildiyini göstərir:

2020/07/06 09:43:08 [POST] incoming HTTP request from 
95.163.216.92:56612
download: s3://myfiles-ash/test.txt to ../../../tmp/myfiles-ash/test.txt
upload: ../../../tmp/myfiles-ash/test.txt to 
s3://myfiles-backup/test.txt

AWS-də myfiles-backup kovasının məzmununu yoxlayaq:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
2020-07-06 09:43:10       1104 test.txt

İndi veb interfeysi vasitəsilə faylı myfiles-kül kovasından siləcəyik.

Server qeydləri:

2020/07/06 09:44:46 [POST] incoming HTTP request from 
95.163.216.92:58224
delete: s3://myfiles-backup/test.txt

Kovanın tərkibi:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$

Fayl silindi, problem həll edildi.

Nəticə və İş

Bu məqalədə istifadə olunan bütün kodlar mənim depomda. Webhookların qeydiyyatı üçün skript nümunələri və imzaların sayılması nümunələri də var.

Bu kod S3 veb-qancalarından öz fəaliyyətinizdə necə istifadə edə biləcəyinizin nümunəsindən başqa bir şey deyil. Başlanğıcda dediyim kimi, istehsalda belə bir serverdən istifadə etməyi planlaşdırırsınızsa, heç olmasa asinxron iş üçün serveri yenidən yazmalısınız: daxil olan veb kancaları növbəyə (RabbitMQ və ya NATS) qeyd edin və oradan onları təhlil edin və emal edin. işçi tətbiqləri ilə. Əks halda, webhooks kütləvi şəkildə gəldiyi zaman, tapşırıqları yerinə yetirmək üçün server resurslarının çatışmazlığı ilə qarşılaşa bilərsiniz. Növbələrin olması server və işçiləri paylamağa, həmçinin nasazlıqlar zamanı təkrar tapşırıqlarla bağlı problemləri həll etməyə imkan verir. Həmçinin, karotajı daha ətraflı və standartlaşdırılmış birinə dəyişdirmək məsləhətdir.

Good Luck!

Mövzu haqqında daha çox oxuyun:

Mənbə: www.habr.com

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