7.1 Neden gerekli?

ASİT'in tüm özelliklerini, amaçlarını ve kullanım durumlarını biraz ayrıntılı olarak tartıştık. Gördüğünüz gibi, tüm veritabanları daha iyi performans için onlardan ödün vererek ACID garantileri sunmaz. Bu nedenle, projenizde ACID sunmayan bir veritabanı seçilmiş olabilir ve uygulama tarafında bazı gerekli ACID işlevlerini uygulamanız gerekebilir. Ve sisteminiz mikro hizmetler veya başka bir tür dağıtılmış uygulama olarak tasarlanmışsa, bir hizmette normal bir yerel işlem olan şey artık dağıtılmış bir işlem haline gelir ve tabii ki, veritabanı kullanılsa bile ASİT niteliğini kaybeder. her bir mikro hizmet ACID olacaktır.

Çok büyük ve karmaşık olduğu için size bir işlem yöneticisinin nasıl oluşturulacağına dair kapsamlı bir rehber vermek istemiyorum ve sadece birkaç temel tekniği ele almak istiyorum. Dağıtılmış uygulamalardan bahsetmiyorsak, ASİT garantilerine ihtiyacınız varsa, uygulama tarafında ASİT'i tam olarak uygulamaya çalışmak için hiçbir neden göremiyorum - sonuçta, hazır bir çözüm almak her anlamda daha kolay ve daha ucuz olacaktır ( yani ASİT içeren bir veri tabanı).

Ancak uygulama tarafında işlem yapmanıza yardımcı olacak bazı teknikleri size göstermek istiyorum. Ne de olsa, bu teknikleri bilmek, işlem gerektirmeyen senaryolarda bile size çeşitli senaryolarda yardımcı olabilir ve sizi daha iyi bir geliştirici yapabilir (umarım öyledir).

7.2 İşlem sevenler için temel araçlar

İyimser ve kötümser engelleme. Bunlar, bazı veriler üzerinde aynı anda erişilebilen iki tür kilittir.

iyimsereşzamanlı erişim olasılığının çok büyük olmadığını varsayar ve bu nedenle aşağıdakileri yapar: istenen satırı okur, sürüm numarasını (veya zaman damgasını veya sağlama toplamı / karma - veri şemasını değiştiremez ve sürüm için bir sütun ekleyemezseniz) hatırlar veya zaman damgası) ve bu veriler için veritabanına değişiklikleri yazmadan önce, bu verilerin sürümünün değişip değişmediğini kontrol eder. Sürüm değiştiyse, oluşturulan çatışmayı bir şekilde çözmeniz ve verileri güncellemeniz ("taahhüt") veya işlemi geri almanız ("geri alma") gerekir. Bu yöntemin dezavantajı, TOCTOU olarak kısaltılan “time-of-check to time-of-use” uzun adı ile bir hata için elverişli koşullar yaratmasıdır: durum, kontrol ve yazma arasındaki zaman diliminde değişebilir. İyimser kilitleme konusunda deneyimim yok,

Örnek olarak, bir geliştiricinin günlük hayatından iyimser kilitleme gibi bir şey kullanan bir teknoloji buldum - bu HTTP protokolüdür. İlk HTTP GET isteğine verilen yanıt, istemciden gelen müteakip PUT istekleri için bir ETag üstbilgisi İÇEREBİLİR ve istemci bunu If-Match üstbilgisinde kullanabilir MAYIS. GET ve HEAD yöntemleri için sunucu, yalnızca bildiği ETag'lerden biriyle eşleşirse istenen kaynağı geri gönderir. PUT ve diğer güvenli olmayan yöntemler için, yalnızca bu durumda da kaynağı yükleyecektir. ETag'in nasıl çalıştığını bilmiyorsanız, burada "feedparser" kitaplığının (RSS ve diğer yayınların ayrıştırılmasına yardımcı olur) kullanımına iyi bir örnek verilmiştir.


>>> import feedparser 
>>> d = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml') 
>>> d.etag 
'"6c132-941-ad7e3080"' 
>>> d2 = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml', etag=d.etag) 
>>> d2.feed 
{} 
>>> d2.debug_message 
'The feed has not changed since you last checked, so the server sent no data.  This is a feature, not a bug!' 

Kötümser ise, işlemlerin genellikle aynı veriler üzerinde "buluşacağı" gerçeğinden hareket eder ve hayatını kolaylaştırmak ve gereksiz yarış koşullarından kaçınmak için ihtiyaç duyduğu verileri bloke eder. Kilitleme mekanizmasını uygulamak için, oturumunuz için bir veritabanı bağlantısını sürdürmeniz (bağlantıları bir havuzdan çekmek yerine - bu durumda büyük olasılıkla iyimser kilitleme ile çalışmak zorunda kalacaksınız) veya işlem için bir kimlik kullanmanız gerekir. , bağlantıdan bağımsız olarak kullanılabilir. Kötümser kilitlemenin dezavantajı, kullanımının genel olarak işlemlerin işlenmesini yavaşlatmasıdır, ancak veriler konusunda sakin olabilir ve gerçek izolasyon elde edebilirsiniz.

Bununla birlikte, birkaç işlemin birbiri tarafından kilitlenen kaynakları beklediği olası kilitlenmede ek bir tehlike pusudadır. Örneğin, bir işlem A ve B kaynaklarını gerektirir. 1. İşlem A kaynağını ve 2. işlem B kaynağını işgal etti. İki işlemden hiçbiri yürütmeye devam edemez. Bu sorunu çözmenin çeşitli yolları var - Şimdi ayrıntılara girmek istemiyorum, bu yüzden önce Wikipedia'yı okuyun ama kısacası, bir kilit hiyerarşisi oluşturma olasılığı var. Bu kavramı daha ayrıntılı olarak tanımak istiyorsanız, o zaman “Yemek Filozofları Problemi” (“yemek filozofları problemi”) üzerinde kafa patlatmaya davetlisiniz.

İşte her iki kilidin de aynı senaryoda nasıl davranacağına dair iyi bir örnek.

Kilit uygulamaları ile ilgili. Ayrıntıya girmek istemiyorum ama dağıtık sistemler için kilit yöneticileri var örneğin: ZooKeeper, Redis, etcd, Consul.

7.3 İşlemlerin yetersizliği

Idempotent kod genellikle iyi bir uygulamadır ve işlemleri kullanıp kullanmadığına bakılmaksızın bir geliştiricinin bunu yapabilmesinin iyi olacağı durum tam olarak budur. Belirsizlik, bir işlemin bir nesneye tekrar uygulandığında aynı sonucu üretme özelliğidir. İşlev çağrıldı - sonucu verdi. Bir veya beş saniye sonra tekrar arandı - aynı sonucu verdi. Elbette veritabanındaki veriler değiştiyse sonuç farklı olacaktır. Üçüncü sistemlerdeki veriler bir işleve bağlı olmayabilir, ancak olan her şey tahmin edilebilir olmalıdır.

Güçsüzlüğün birkaç tezahürü olabilir. Bunlardan biri, kodunuzu nasıl yazacağınıza dair bir öneridir. En iyi fonksiyonun bir şeyi yapan fonksiyon olduğunu hatırlıyor musunuz? Ve bu işlev için birim testleri yazmak için iyi bir şey ne olabilir? Bu iki kurala bağlı kalırsanız, işlevlerinizin önemsiz olma şansını zaten artırmış olursunuz. Karışıklığı önlemek için, idempotent fonksiyonların mutlaka "saf" olmadığını ("işlev saflığı" anlamında) açıklığa kavuşturacağım. Saf fonksiyonlar, sadece girişte aldıkları veriler üzerinde çalışan, onları hiçbir şekilde değiştirmeden ve işlenen sonucu döndürmeyen fonksiyonlardır. Bunlar, fonksiyonel programlama tekniklerini kullanarak uygulamanızı ölçeklendirmenize izin veren fonksiyonlardır. Bazı genel verilerden ve bir veritabanından bahsettiğimiz için, fonksiyonlarımızın saf olması pek olası değildir.

Bu saf bir işlevdir:


def square(num: int) -> int: 
	return num * num 

Ancak bu işlev saf değil, önemsizdir (lütfen bu parçalardan nasıl kod yazdığım hakkında sonuçlar çıkarmayın):


def insert_data(insert_query: str, db_connection: DbConnectionType) -> int: 
  db_connection.execute(insert_query) 
  return True 

Bir sürü kelime yerine, nasıl idempotent programlar yazmayı öğrenmeye zorlandığım hakkında konuşabilirim. Şimdiye kadar gördüğünüz gibi AWS ile çok iş yapıyorum ve AWS Lambda adında bir hizmet var. Lambda, sunucularla ilgilenmenize değil, yalnızca bazı olaylara yanıt olarak veya bir programa göre çalışacak kodu yüklemenize izin verir. Bir olay, bir mesaj aracısı tarafından teslim edilen mesajlar olabilir. AWS'de bu aracı, AWS SNS'dir. Bunun AWS ile çalışmayanlar için bile açık olması gerektiğini düşünüyorum: kanallar ("konular") aracılığıyla mesaj gönderen bir aracımız var ve bu kanallara abone olan mikro hizmetler mesajları alıyor ve bir şekilde onlara tepki veriyor.

Sorun, SNS'nin mesajları "en az bir kez" ("en az bir kez teslimat") teslim etmesidir. Bu ne anlama geliyor? Er ya da geç Lambda kodunuz iki kez çağrılacak. Ve gerçekten oluyor. İşlevinizin önemsiz olması gereken birkaç senaryo vardır: örneğin, bir hesaptan para çekildiğinde, birinin aynı tutarı iki kez çekmesini bekleyebiliriz, ancak bunların gerçekten 2 bağımsız zaman olduğundan emin olmamız gerekir - yani bunlar 2 farklı işlemdir ve birinin tekrarı değildir.

Bir değişiklik için, başka bir örnek vereceğim - API'ye yapılan isteklerin sıklığını sınırlama ("hız sınırlaması"). Lambda'mız, belirli bir user_id ile bir olay alır ve bu kimliğe sahip kullanıcının bazı API'larımıza yönelik olası isteklerini tüketip tüketmediğini görmek için bir kontrol yapılması gerekir. AWS'den DynamoDB'de yapılan çağrıların değerini saklayabilir ve fonksiyonumuza yapılan her çağrıda değeri 1 artırabiliriz.

Ancak bu Lambda işlevi aynı olay tarafından iki kez çağrılırsa ne olur? Bu arada, lambda_handler() fonksiyonunun argümanlarına dikkat ettiniz mi? AWS Lambda'daki ikinci bağımsız değişken olan bağlam, varsayılan olarak verilir ve her benzersiz çağrı için oluşturulan request_id dahil olmak üzere çeşitli meta verileri içerir. Bu, artık tabloda yapılan çağrıların sayısını saklamak yerine request_id listesini saklayabileceğimiz ve her çağrıda Lambda'mızın verilen talebin daha önce işlenip işlenmediğini kontrol edeceği anlamına gelir:

import json
import os
from typing import Any, Dict

from aws_lambda_powertools.utilities.typing import LambdaContext  # needed only for argument type annotation
import boto3

limit = os.getenv('LIMIT')

def handler_name(event: Dict[str: Any], context: LambdaContext):

	request_id = context.aws_request_id

	# We find user_id in incoming event
	user_id = event["user_id"]

	# Our table for DynamoDB
	table = boto3.resource('dynamodb').Table('my_table')

	# Doing update
	table.update_item(
    	Key={'pkey': user_id},
    	UpdateExpression='ADD requests :request_id',
    	ConditionExpression='attribute_not_exists (requests) OR (size(requests) < :limit AND NOT contains(requests, :request_id))',
    	ExpressionAttributeValues={
        	':request_id': {'S': request_id},
        	':requests': {'SS': [request_id]},
        	':limit': {'N': limit}
    	}
	)

	# TODO: write further logic

	return {
    	"statusCode": 200,
    	"headers": {
        	"Content-Type": "application/json"
    	},
    	"body": json.dumps({
        	"status ": "success"
    	})
	}

Örneğim aslında internetten alındığı için, özellikle biraz daha fazla bilgi verdiği için orijinal kaynağa bir bağlantı bırakacağım .

Paylaşılan verileri kilitlemek için benzersiz bir işlem kimliği gibi bir şeyin kullanılabileceğinden daha önce nasıl bahsettiğimi hatırlıyor musunuz? Artık işlemleri önemsiz kılmak için de kullanılabileceğini öğrendik. Bu tür kimlikleri hangi yollarla kendiniz oluşturabileceğinizi öğrenelim.