Scala ilə sürətli və sağlam bir REST API yaradın

"Bir pişiyi dərinin birdən çox yolu var."

Bu məşhur bir kəlamdır və zehni görüntü narahat edici olsa da, xüsusən kompüter elmi üçün universal bir həqiqətdir.

Beləliklə, RAL API'sini Scala-da qurmağın yolu deyil, qurmağın bir yolu.

Rahatlıq üçün, deyək ki, istifadəçilərin profillərinə daxil olmaq və yeniləmələr təqdim etmək üçün istifadə edə biləcəyi Reddit kimi bir tətbiq üçün bəzi API'lər yaradırıq. Reddit metaforasına əsaslanmaq üçün api / v1 / me və api / submit (new) tətbiq etdiyimizi təsəvvür edin.

Bəzi torpaq işləri

Bir sözlə:

  1. Scala, Lambda hesablamasına əsaslanan, Java Virtual Maşın üzərində işləyən və Java ilə problemsiz şəkildə inteqrasiya olunan obyekt yönümlü bir proqramlaşdırma dilidir.
  2. AKKA, Scala bazasında inşa edilmiş, aktyorlar (çox iş parçalı təhlükəsiz obyektlər) və daha çoxunu təqdim edən bir kitabxanadır.
  3. Spray.io, AKKA üzərində qurulan və öz bulud xidmətinizi həyata keçirə bilmək üçün HTTP protokolunun sadə, çevik bir tətbiqini təklif edən bir HTTP kitabxanasıdır.

Çətinlik

REST API-nin aşağıdakıları təmin edəcəyi gözlənilir:

  1. Sürətli, etibarlı zəng səviyyəsində identifikasiya və icazə nəzarəti;
  2. sürətli iş məntiqi hesablama və giriş / çıxış;
  3. yuxarıdakıların hamısı yüksək dərəcədə bir paralellik ilə;
  4. tez qeyd etdim?

Addım 1, doğrulama və icazə

Doğrulama OAUTH və ya OAUTH 2-də və ya özəl / açıq açar ilə identifikasiyanın bir variantında həyata keçirilməlidir.

OAUTH2 yanaşmasının üstünlüyü ondan ibarətdir ki, bir oturum işarəsi (müvafiq istifadəçi hesabı və sessiya əldə etmək üçün istifadə edə bilərsiniz) və bir anda imza işarəsi əldə etməlisiniz.

İstifadə etdiyimiz şeyin bu olduğunu düşünərək burada davam edəcəyik.

İmza işarəsi ümumiyyətlə sorğunun bütün yükünü SHA1 istifadə edərək paylaşılan gizli açarla imzalayaraq əldə edilən şifrələnmiş bir əlamətdir. İmza işarəsi bir daşla iki quşu öldürür:

  1. Burada zəng edən şəxsin düzgün paylaşılan sirri bildiyini öyrənə bilərsiniz.
  2. məlumat enjeksiyonu və ortada insan hücumlarının qarşısını alır;

Bunun üçün bəzi xərclər var: birincisi, məlumatları giriş / çıxarma qatınızdan çıxarmalısınız, ikincisi, zəng edən şəxsin imza işarəsini və serverin imzasını müqayisə etmədən əvvəl nisbətən bahalı bir şifrələməni (yəni SHA1) hesablamalısınız. arxa tərəf (demək olar ki) hamısını bildiyindən doğru olan hesab olunur.

Önbellek (Memcache? Redis?) I / O-nu dəstəkləmək üçün əlavə edilə bilər və davamlı yığına (Mongo? Postgres?) Daha bahalı səyahət tələb olunmur.

AKKA və Spray.io yuxarıdakı problemlərin həllində çox təsirli olur. Spray.io, HTTP başlıq məlumatını və faydalı yükü çıxarmaq üçün lazım olan addımları əhatə edir. AKKA aktyorları, API analizindən asılı olmayaraq asenkron tapşırıqları yerinə yetirməyə imkan verir. Bu birləşmə istək işləyicisindəki yükü azaldır və API-lərin çoxunun işləmə müddəti 100 ms-dən az olması üçün etalonlaşdırıla bilər. Qeyd: İşləmə müddətinin cavab vaxtı olmadığını göstərdim. Şəbəkə gecikməsi nəzərə alınmır.

Qeyd: AKKA aktyorları ilə, eyni zamanda çalışan iki proses tetiklenebilir, biri avtorizasiya / kimlik doğrulaması, digəri iş məntiqi üçün. Daha sonra onların geri çağırılması üçün qeydiyyatdan keçər və nəticələri birləşdirərdiniz. Bu, identifikasiyanın müvəffəq olacağına dair optimist yanaşma edərək API tətbiqini zəng səviyyəsində paralelləşdirir. Bu yanaşma, müştərinin iş məntiqinə ehtiyac duyduğu hər şeyi göndərməsi məcburiyyətində olduğu üçün minimal məlumat təkrarlanmasını tələb edir, məsələn: İstifadəçi identifikatoru və normal olaraq sessiyadan çıxaracağınız hər şey kimi. Təcrübəmə görə, bu yanaşmanın tətbiqi icra müddətində təxminən 10% azalma ilə nəticələnir və daha çox CPU və daha çox yaddaş istifadə etdiyi üçün həm dizayn zamanı, həm də iş vaxtında baha başa gəlir. Bununla birlikdə nisbətən aşağı qazancın dəqiqədə milyonlarla zənglərin işlənməsi, qənaət / faydaların artırılması ilə əlaqəli ssenarilər ola bilər. Lakin, əksər hallarda, bunu tövsiyə etməzdim.

Bir istifadəçi üçün oturum işarəsi həll edildikdən sonra, icazə səviyyəsinə sahib istifadəçi profili önbelleğe alınır və API zəngi üçün tələb olunan icazə səviyyəsi ilə müqayisə edilə bilər.

Bir API-nin icazə səviyyəsini əldə etmək üçün biri URI-ni təhlil edir və REST mənbəyini və identifikatorunu çıxarır (əgər varsa) və növü çıxarmaq üçün HTTP başlığını istifadə edir.

Tutaq ki, qeydiyyatdan keçmiş istifadəçilərin bir HTTP GET vasitəsilə profillərini əldə etməsinə icazə vermək istəyirsiniz

/ api / v1 / me

o zaman belə bir sistemdə icazə konfiqurasiya sənədinin görünməsi budur:

{"V1 / me": [{"Admin": ["get", "put", "post", "delete"]}, {"Registered": ["get", "put", "post", "sil"]}, {"Oxu_yalnız": ["almaq"]}, {"tıkanmış": []}], "təqdim et": [{"İdarəetmə": ["qoydu", "göndər", "sil" ]}, {"Qeydiyyata alındı": ["Yazı", "Sil"]}, {"Yalnız oxunur": []}, {"Tıxanmış": []}]}

Oxucu qeyd etməlidir ki, bu, məlumatlara giriş icazəsi üçün zəruri, lakin kifayət deyil. İndiyə qədər müəyyən etdik ki, zəng edən müştəriyə zəng etmək səlahiyyəti verilib və istifadəçinin API-yə daxil olmaq icazəsi var. Bununla yanaşı, bir çox hallarda, İstifadəçi A-nın İstifadəçi B-nin məlumatlarını görə bilməməsini (və ya redaktə edə bilməməsini) təmin etməliyik. Beləliklə, qeydləri "get_owner" ilə genişləndirək, yəni təsdiqlənmiş istifadəçilər yalnız bir mənbənin sahibi olduqları təqdirdə bir GET həyata keçirmək səlahiyyətinə sahibdirlər. Gəlin konfiqurasiyanın necə olacağını görək:

{"V1 / me": [{"Admin": ["get", "put", "post", "delete"]}, {"Registered": ["get_owner", "put", "post", "silmək"]}, {"Yalnızca oxu_": ["get_owner"]}, {"tıxanmış": []}], "təqdim et": [{"İdarəetmə": ["qoydu", "yazı", "silmək" ]}, {"Qeydiyyata alındı": ["put_owner", "post", "delete"]}, {"write-protected": []}, {"engellendi": []}]}

Artıq qeydiyyatdan keçmiş bir istifadəçi öz profilinə daxil ola, oxuya və dəyişdirə bilər, ancaq başqası edə bilməz (administrator istisna olmaqla). Eynilə, yalnız sahibi bir yazını aşağıdakılarla yeniləyə bilər:

/ api / təqdim /

Bu yanaşmanın gücü ondan ibarətdir ki, icazə konfiqurasiyasını sadəcə dəyişdirmək, istifadəçilərin məlumatlarla edə biləcəyi şeylərdə dramatik dəyişikliklər edə bilər. Kod dəyişikliyinə ehtiyac yoxdur. Bu şəkildə, arxa tərəf, məhsulun həyat dövrü ərzində dəyişiklikləri dərhal tələblərə uyğunlaşdıra bilər.

İcazə API-nin iş məntiqi ilə əlaqəli olmayan və yalnız identifikasiya və avtorizasiyanı tətbiq edən və tətbiq edən bir sıra funksiyalara daxil edilə bilər:

def validateSessionToken (sessionToken: String) UserProfile = {...} def checkPermission (Metod: String, Resurs: Character string, User: UserProfile) {... // Xəta halında istisna atır
}

Bunlara API zəngləri ilə işləyən Spray.io başında deyilir:

// DİQQƏT: profileReader və sumbissionWriter, AKKA aktyorunu uzatdıqları təqdirdə buraxılır.
def route = {pathPrefix ("api") {
// başlıqları və HTTP məlumatlarını çıxarın
... var user: UserProfile = null Sınayın {validatedSessionToken (sessionToken)} catch (e: Exception) {complete (completeWithError (e.getMessage))}
{CheckPermission (metod, kaynak, user)} catch (e: Exception) {complete (completeWithError (e.getMessage))} cəhd edin
pathPrefix ("v1") {yol ("mən") {qəbul edildi {tamamlandı (profileReader? getUserProfile (user.id))}
}
} ~ yol ("təqdim et") {yazı {
varlıq (kimi [String]) {=> jsonstr val payload = read [SubmitPayload] (jsonstr)
tamamlandı (submitWriter? sumbit (faydalı yük))
}}
} ...
}

Gördüyümüz kimi, bu şəkildə Spray.io işləyicisi oxunaqlı və qorumaq asandır, çünki identifikasiya / avtorizasiya hər API-nin fərdi iş məntiqindən ayrılır. Burada göstərilməyən məlumat sahibliyinin icrası, Boole dəyərinin G / Ç qatına ötürülməsi ilə əldə edilə bilər və bu da istifadəçi məlumatlarının davamlılıq səviyyəsində mülkiyyətini təmin edir.

Addım 2, iş məntiqi

İş məntiqi yuxarıdakı kod parçasında göstərilən SubmissionWriter kimi I / O aktyorlarında yerləşdirilə bilər. Bu aktyor əvvəl Elasticsearch kimi önbellek qatına, sonra da seçdiyiniz bir verilənlər bazasına yazan asinxron bir G / Ç əməliyyatı həyata keçirəcəkdir. DB yazıları daha da atəşə ayrılır və müştəri bu bahalı əməliyyatların tamamlanmasını gözləməməsi üçün log əsaslı bərpa istifadə edən məntiqi unuda bilər.

Diqqət yetirin ki, bu nikbin, kilidlənməyən bir yanaşmadır və müştərinin məlumatların yazıldığına əmin ola biləcəyi yeganə yol bir oxunuşun həyata keçirilməsidir. O vaxta qədər bir mobil müştəri, müvafiq yaddaş məlumatlarının çirkli olduğu fərziyyəsi altında işləməlidir.

Bu, çox güclü bir dizayn paradiqmasıdır, bununla birlikdə oxucuya AKKA + Spary.io-nun aktyor zəng yığınına üç səviyyədən çox addım atmağınıza icazə verməyəcəyi xəbərdar edilməlidir. Məsələn, sistemdəki aktyorlar bunlardırsa:

  1. Sprey router üçün S.
  2. API işləyicisi üçün A.
  3. G / Ç işləyicisi üçün B.

Siz x almaq zaman? X-nin y-yə zəng vuracağını və geri çağırmağı tələb edəcəyini bildirmək üçün y qeydini istifadə edin və x! y x atəş deməkdir və y unudar, işləyən budur:

S? BİR! B.

Lakin bunlar yoxdur:

S! BİR! B.

S? BİR! B! B.

Bu iki vəziyyətdə də, A o qədər təsirli bir şəkildə tamamlandıqda, yüklədiyiniz hesablamanı atəşə qoymaq və aktyoru unutmaq üçün yalnız bir şansınız olacaqsa, bütün B nümunələri məhv olacaqdır. Bunun AKKA deyil, bir sprey məhdudiyyəti olduğuna inanıram və bu yazı zamanı düzəldilə bilərdi.

Nəhayət, G / Ç və inad

Yuxarıda göstərildiyi kimi, API POST / PUT performansını məqbul icra müddəti ərzində saxlamaq üçün yavaş yazılar asinxron mövzulara yönəldilə bilər. Bunlar ümumiyyətlə server profilindən və atəşi unutma yanaşmasından istifadə edərək nə qədər məntiqin təxirə salına biləcəyinə görə bir neçə on saniyə və ya yüz milisaniyədən az aralığındadır.

Bununla birlikdə, oxunan yazı sayının bir və ya daha çox sərəncamdan çox olması tez-tez olur. Bu səbəbdən önbelleğe yaxşı bir yanaşma yüksək ümumi məhsuldarlıq əldə etmək üçün vacibdir.

Qeyd: düyünlərdən gələn sensor məlumatların oxunan dəyərdən bir neçə dərəcə yuxarı olduğu IOT mənzərələri üçün əks şeydir. Bu vəziyyətdə, mənzərə bir server qrupunu yalnız IoT cihazlarından yazmaq üçün konfiqurasiya etmək üçün konfiqurasiya edilə bilər və bununla da müştərilərdən API çağırışlarına fərqli spesifikasiyalı başqa bir server qrupu təyin edir (ön uç) . Kod bazalarının hamısı olmasa da əksəriyyəti bu iki server sinfi arasında paylaşıla bilər və təhlükəsizlik boşluqlarının qarşısını almaq üçün xüsusiyyətləri konfiqurasiya yolu ilə asanlıqla söndürülə bilər.

Məşhur bir yanaşma, Redis kimi yaddaş yaddaşından istifadə etməkdir. Redis istifadəçi hüquqlarını identifikasiya üçün saxlayarkən yaxşı nəticə verir; H. Tez-tez dəyişməyən tarixlər. Tək bir Redis nodu 250 mil cütə qədər saxlaya bilər.

Önbelleği sorgulamalı olan oxumalar üçün fərqli bir həll yolu lazımdır. Yaddaşdakı bir göstərici olan Elasticsearch, coğrafi məlumatlar və ya növlərə bölünə bilən məlumatlar üçün əladır. Məsələn, Köpəklər və Motosikl tipləri ilə Göndərmə adlı bir indeks asanlıqla müəyyən mövzular üçün ən son subredditləri əldə etmək üçün sorğu edilə bilər.

Məsələn, Elasticsearch-in http API qeydini istifadə edin:

curl -XPOST 'localhost: 9200 / təqdimlər / itlər / _suche? olduqca '-d' {"sorğu": {"süzülmüş": {"sorğu": {"match_all": {}}, "filtr": {"təklif": {"yaradılmış": {"gte": 1464913588000} }}}}} '

/ itlərdə göstərilən tarixdən sonra bütün sənədləri qaytaracaqdı. Sənədlərində “Ducati” əsəri olan / Göndərmə / Motosikllərdəki bütün təqdimatları da axtara bildik.

curl -XPOST 'localhost: 9200 / submit / motosiklet / _search? olduqca '-d' {"sorğu": {"uyğunluq": {"mətn": "Ducati"}}} ''
İndeks məlumat daxil edilməzdən əvvəl diqqətlə hazırlanmış və qurulmuşsa Elasticsearch oxumaq üçün çox yaxşıdır. Bu, bəzilərinin qarşısını ala bilər, çünki Elasticsearch-in üstünlüklərindən biri sadəcə sənəd göndərmək və mühərrikin növlərini və məlumat strukturlarını kəşf etməsinə icazə verməklə indeks yaratmaq qabiliyyətidir. Bununla birlikdə, quruluşu müəyyənləşdirməyin faydaları maliyyəni üstələyir və yeni bir indeksə köçün istehsalın yüngülləşdirilmiş mühitlərində asan olduğunu qeyd etmək lazımdır.

Qeyd: Elastik axtarış indeksləri balanslaşdırılmış ağac kimi tətbiq olunur. İndekslərin daxil edilməsi və silinməsi böyük ağaclarda bahalı ola bilər. On milyonlarla sənədin indeksinə daxil edilmək, serverin xüsusiyyətlərindən asılı olaraq on saniyə çəkə bilər. Bu, Elasticsearch yazılarınızın buludunuzdakı ən yavaş proseslər arasında olmasına səbəb ola bilər (əlbəttə ki, DB yazıları xaricində). Ancaq yazıları atəşə atarsanız və AKKA aktyorunu unutursanız, həll etməsən problem daha yaxşı ola bilər.

Nəticələr

Scala + AKKA + Spray.io, yaddaş yaddaşında saxlanılması və / və ya yaddaş indeksləşdirilməsi ilə evli olsanız, yüksək performanslı REST API yaratmaq üçün çox təsirli bir texnoloji yığınıdır.

Burada təsvir olunan konsepsiyalardan çox uzaq olmayan bir tətbiq üzərində işləyirdim, hər düymə başına dəqiqədə 2000 vuruş CPU yükünü 1% -dən çox çəkdi.

Bonus Dəyirmi: Maşın Öyrənmə və Daha çoxu

Partiyaya Elasticsearch əlavə etmək, Apache Spark içərisində Elasticsearch qurulduğu üçün daxili və oflayn maşın öyrənmə qapılarını açır. API üçün istifadə olunan eyni davamlılıq təbəqəsi, maşın öyrənmə modulları tərəfindən yenidən istifadə edilə bilər, kodlaşdırma, təmir xərcləri və yığma mürəkkəbliyini azaldır. Nəhayət, Scala ilə, Stanford'un əsas NLP, OpenCV, Spark Mlib və daha çoxundan faydalanaraq daha mürəkkəb hesablamaların qapısını açan hər hansı bir Scala və ya Java kitabxanasından istifadə edə bilərik.

Bu yazıda qeyd olunan texnologiyalara istinadlar

  1. http://www.scala-lang.org
  2. http://spray.io
  3. və bununla (2) mənalı olaraq http://akka.io-ya nəzər yetirin