بسیاری از برنامه های بلادرنگ اسنادی دارند که به عنوان شمارنده عمل می کنند. به عنوان مثال، ممکن است «لایک» در یک پست یا «موارد دلخواه» یک مورد خاص را بشمارید.
در Cloud Firestore ، نمیتوانید یک سند را با نرخ نامحدود بهروزرسانی کنید. اگر یک شمارنده بر اساس سند واحد داشته باشید و به اندازه کافی مکرر آن را افزایش دهید، در نهایت شاهد مشاجره در مورد به روز رسانی سند خواهید بود. بهروزرسانیهای یک سند را ببینید.
راه حل: شمارنده های توزیع شده
برای پشتیبانی از بهروزرسانیهای مکرر شمارنده، یک شمارنده توزیعشده ایجاد کنید. هر شمارنده سندی است با زیر مجموعه ای از «شاردها» و مقدار شمارنده مجموع ارزش خرده ها است.
توان نوشتن با تعداد خرده ها به صورت خطی افزایش می یابد، بنابراین یک شمارنده توزیع شده با 10 قطعه می تواند 10 برابر تعداد نوشته های یک شمارنده سنتی را مدیریت کند.
وب
// counters/${ID}
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// counters/${ID}/shards/${NUM}
{
"count": 123
}
سویفت
// counters/${ID} struct Counter { let numShards: Int init(numShards: Int) { self.numShards = numShards } } // counters/${ID}/shards/${NUM} struct Shard { let count: Int init(count: Int) { self.count = count } }
هدف-C
// counters/${ID} @interface FIRCounter : NSObject @property (nonatomic, readonly) NSInteger shardCount; @end @implementation FIRCounter - (instancetype)initWithShardCount:(NSInteger)shardCount { self = [super init]; if (self != nil) { _shardCount = shardCount; } return self; } @end // counters/${ID}/shards/${NUM} @interface FIRShard : NSObject @property (nonatomic, readonly) NSInteger count; @end @implementation FIRShard - (instancetype)initWithCount:(NSInteger)count { self = [super init]; if (self != nil) { _count = count; } return self; } @end
Kotlin+KTX
// counters/${ID} data class Counter(var numShards: Int) // counters/${ID}/shards/${NUM} data class Shard(var count: Int)
Java
// counters/${ID} public class Counter { int numShards; public Counter(int numShards) { this.numShards = numShards; } } // counters/${ID}/shards/${NUM} public class Shard { int count; public Shard(int count) { this.count = count; } }
پایتون
Python
Node.js
قابل اجرا نیست، قطعه افزایش شمارنده را در زیر ببینید.
برو
PHP
قابل اجرا نیست، قطعه مقداردهی اولیه شمارنده را در زیر ببینید.
سی شارپ
کد زیر یک شمارنده توزیع شده را مقداردهی اولیه می کند:
وب
function createCounter(ref, num_shards) { var batch = db.batch(); // Initialize the counter document batch.set(ref, { num_shards: num_shards }); // Initialize each shard with count=0 for (let i = 0; i < num_shards; i++) { const shardRef = ref.collection('shards').doc(i.toString()); batch.set(shardRef, { count: 0 }); } // Commit the write batch return batch.commit(); }
سویفت
func createCounter(ref: DocumentReference, numShards: Int) async { do { try await ref.setData(["numShards": numShards]) for i in 0...numShards { try await ref.collection("shards").document(String(i)).setData(["count": 0]) } } catch { // ... } }
هدف-C
- (void)createCounterAtReference:(FIRDocumentReference *)reference shardCount:(NSInteger)shardCount { [reference setData:@{ @"numShards": @(shardCount) } completion:^(NSError * _Nullable error) { for (NSInteger i = 0; i < shardCount; i++) { NSString *shardName = [NSString stringWithFormat:@"%ld", (long)shardCount]; [[[reference collectionWithPath:@"shards"] documentWithPath:shardName] setData:@{ @"count": @(0) }]; } }]; }
Kotlin+KTX
fun createCounter(ref: DocumentReference, numShards: Int): Task<Void> { // Initialize the counter document, then initialize each shard. return ref.set(Counter(numShards)) .continueWithTask { task -> if (!task.isSuccessful) { throw task.exception!! } val tasks = arrayListOf<Task<Void>>() // Initialize each shard with count=0 for (i in 0 until numShards) { val makeShard = ref.collection("shards") .document(i.toString()) .set(Shard(0)) tasks.add(makeShard) } Tasks.whenAll(tasks) } }
Java
public Task<Void> createCounter(final DocumentReference ref, final int numShards) { // Initialize the counter document, then initialize each shard. return ref.set(new Counter(numShards)) .continueWithTask(new Continuation<Void, Task<Void>>() { @Override public Task<Void> then(@NonNull Task<Void> task) throws Exception { if (!task.isSuccessful()) { throw task.getException(); } List<Task<Void>> tasks = new ArrayList<>(); // Initialize each shard with count=0 for (int i = 0; i < numShards; i++) { Task<Void> makeShard = ref.collection("shards") .document(String.valueOf(i)) .set(new Shard(0)); tasks.add(makeShard); } return Tasks.whenAll(tasks); } }); }
پایتون
Python
Node.js
قابل اجرا نیست، قطعه افزایش شمارنده را در زیر ببینید.
برو
PHP
سی شارپ
روبی
برای افزایش شمارنده، یک قطعه تصادفی انتخاب کنید و تعداد را افزایش دهید:
وب
function incrementCounter(ref, num_shards) { // Select a shard of the counter at random const shard_id = Math.floor(Math.random() * num_shards).toString(); const shard_ref = ref.collection('shards').doc(shard_id); // Update count return shard_ref.update("count", firebase.firestore.FieldValue.increment(1)); }
سویفت
func incrementCounter(ref: DocumentReference, numShards: Int) { // Select a shard of the counter at random let shardId = Int(arc4random_uniform(UInt32(numShards))) let shardRef = ref.collection("shards").document(String(shardId)) shardRef.updateData([ "count": FieldValue.increment(Int64(1)) ]) }
هدف-C
- (void)incrementCounterAtReference:(FIRDocumentReference *)reference shardCount:(NSInteger)shardCount { // Select a shard of the counter at random NSInteger shardID = (NSInteger)arc4random_uniform((uint32_t)shardCount); NSString *shardName = [NSString stringWithFormat:@"%ld", (long)shardID]; FIRDocumentReference *shardReference = [[reference collectionWithPath:@"shards"] documentWithPath:shardName]; [shardReference updateData:@{ @"count": [FIRFieldValue fieldValueForIntegerIncrement:1] }]; }
Kotlin+KTX
fun incrementCounter(ref: DocumentReference, numShards: Int): Task<Void> { val shardId = Math.floor(Math.random() * numShards).toInt() val shardRef = ref.collection("shards").document(shardId.toString()) return shardRef.update("count", FieldValue.increment(1)) }
Java
public Task<Void> incrementCounter(final DocumentReference ref, final int numShards) { int shardId = (int) Math.floor(Math.random() * numShards); DocumentReference shardRef = ref.collection("shards").document(String.valueOf(shardId)); return shardRef.update("count", FieldValue.increment(1)); }
پایتون
Python
Node.js
برو
PHP
سی شارپ
روبی
برای بدست آوردن تعداد کل، همه خرده ها را پرس و جو کنید و فیلدهای count
آنها را جمع کنید:
وب
function getCount(ref) { // Sum the count of each shard in the subcollection return ref.collection('shards').get().then((snapshot) => { let total_count = 0; snapshot.forEach((doc) => { total_count += doc.data().count; }); return total_count; }); }
سویفت
func getCount(ref: DocumentReference) async { do { let querySnapshot = try await ref.collection("shards").getDocuments() var totalCount = 0 for document in querySnapshot.documents { let count = document.data()["count"] as! Int totalCount += count } print("Total count is \(totalCount)") } catch { // handle error } }
هدف-C
- (void)getCountWithReference:(FIRDocumentReference *)reference { [[reference collectionWithPath:@"shards"] getDocumentsWithCompletion:^(FIRQuerySnapshot *snapshot, NSError *error) { NSInteger totalCount = 0; if (error != nil) { // Error getting shards // ... } else { for (FIRDocumentSnapshot *document in snapshot.documents) { NSInteger count = [document[@"count"] integerValue]; totalCount += count; } NSLog(@"Total count is %ld", (long)totalCount); } }]; }
Kotlin+KTX
fun getCount(ref: DocumentReference): Task<Int> { // Sum the count of each shard in the subcollection return ref.collection("shards").get() .continueWith { task -> var count = 0 for (snap in task.result!!) { val shard = snap.toObject<Shard>() count += shard.count } count } }
Java
public Task<Integer> getCount(final DocumentReference ref) { // Sum the count of each shard in the subcollection return ref.collection("shards").get() .continueWith(new Continuation<QuerySnapshot, Integer>() { @Override public Integer then(@NonNull Task<QuerySnapshot> task) throws Exception { int count = 0; for (DocumentSnapshot snap : task.getResult()) { Shard shard = snap.toObject(Shard.class); count += shard.count; } return count; } }); }
پایتون
Python
Node.js
برو
PHP
سی شارپ
روبی
محدودیت ها
راه حل نشان داده شده در بالا یک روش مقیاس پذیر برای ایجاد شمارنده های مشترک در Cloud Firestore است، اما باید از محدودیت های زیر آگاه باشید:
- تعداد خرده ها - تعداد خرده ها عملکرد شمارنده توزیع شده را کنترل می کند. با تعداد خردههای بسیار کم، ممکن است برخی از تراکنشها قبل از موفقیت دوباره تلاش کنند، که سرعت نوشتن را کاهش میدهد. با تعداد زیاد خردهها، خواندن کندتر و گرانتر میشود. میتوانید هزینه خواندن را با نگهداشتن مجموع شمارنده در یک سند جمعآوری جداگانه که با سرعت کمتری بهروزرسانی میشود، جبران کنید و از مشتریان بخواهید که از آن سند بخوانند تا کل را به دست آورند. مبادله این است که مشتریان باید منتظر بمانند تا سند جمع آوری شده به روز شود، به جای محاسبه کل با خواندن همه خرده ها بلافاصله پس از هر به روز رسانی.
- هزینه - هزینه خواندن یک مقدار شمارنده به صورت خطی با تعداد خرده ها افزایش می یابد، زیرا کل زیر مجموعه خرده ها باید بارگذاری شود.