Nhiều ứng dụng thời gian thực có tài liệu đóng vai trò là bộ đếm. Ví dụ: bạn có thể tính 'lượt thích' trên một bài đăng hoặc 'số lượt yêu thích' của một mục cụ thể.
Trong Cloud Firestore, bạn không thể cập nhật một tài liệu với tốc độ không giới hạn. Nếu bạn có bộ đếm dựa trên một tài liệu duy nhất và số gia tăng đủ thường xuyên cho nó thì cuối cùng bạn sẽ thấy sự tranh chấp về các bản cập nhật của tài liệu. Xem phần Cập nhật cho một tài liệu .
Giải pháp: Bộ đếm phân tán
Để hỗ trợ cập nhật bộ đếm thường xuyên hơn, hãy tạo bộ đếm phân tán. Mỗi bộ đếm là một tài liệu có một tập hợp con gồm các "phân đoạn" và giá trị của bộ đếm là tổng giá trị của các phân đoạn.
Thông lượng ghi tăng tuyến tính theo số lượng phân đoạn, do đó, bộ đếm phân tán có 10 phân đoạn có thể xử lý số lần ghi nhiều gấp 10 lần so với bộ đếm truyền thống.
Web
// counters/${ID}
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// counters/${ID}/shards/${NUM}
{
"count": 123
}
Nhanh
// 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 } }
Mục tiêu-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
Python
Node.js
Không áp dụng được, hãy xem đoạn mã tăng số lượt truy cập bên dưới.
Đi
PHP
Không áp dụng, hãy xem đoạn mã khởi tạo bộ đếm bên dưới.
C#
Đoạn mã sau khởi tạo một bộ đếm phân tán:
Web
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(); }
Nhanh
func createCounter(ref: DocumentReference, numShards: Int) { ref.setData(["numShards": numShards]){ (err) in for i in 0...numShards { ref.collection("shards").document(String(i)).setData(["count": 0]) } } }
Mục tiêu-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
Python
Node.js
Không áp dụng được, hãy xem đoạn mã tăng số lượt truy cập bên dưới.
Đi
PHP
C#
hồng ngọc
Để tăng bộ đếm, chọn một phân đoạn ngẫu nhiên và tăng số lượng:
Web
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)); }
Nhanh
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)) ]) }
Mục tiêu-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
Python
Node.js
Đi
PHP
C#
hồng ngọc
Để có được tổng số, hãy truy vấn tất cả các phân đoạn và tính tổng các trường count
của chúng:
Web
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; }); }
Nhanh
func getCount(ref: DocumentReference) { ref.collection("shards").getDocuments() { (querySnapshot, err) in var totalCount = 0 if err != nil { // Error getting shards // ... } else { for document in querySnapshot!.documents { let count = document.data()["count"] as! Int totalCount += count } } print("Total count is \(totalCount)") } }
Mục tiêu-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
Python
Node.js
Đi
PHP
C#
hồng ngọc
Hạn chế
Giải pháp được trình bày ở trên là một cách có thể mở rộng để tạo bộ đếm dùng chung trong Cloud Firestore, nhưng bạn nên lưu ý những hạn chế sau:
- Số lượng phân đoạn - Số lượng phân đoạn kiểm soát hiệu suất của bộ đếm phân tán. Với quá ít phân đoạn, một số giao dịch có thể phải thử lại trước khi thành công, điều này sẽ làm chậm quá trình ghi. Với quá nhiều phân đoạn, quá trình đọc sẽ trở nên chậm hơn và tốn kém hơn. Bạn có thể bù đắp chi phí đọc bằng cách giữ tổng số tiền trong một tài liệu cuộn lên riêng biệt được cập nhật với nhịp độ chậm hơn và yêu cầu khách hàng đọc từ tài liệu đó để biết tổng chi phí. Sự cân bằng là khách hàng sẽ phải đợi tài liệu tổng hợp được cập nhật, thay vì tính tổng bằng cách đọc tất cả các phân đoạn ngay sau bất kỳ cập nhật nào.
- Chi phí - Chi phí đọc giá trị bộ đếm tăng tuyến tính theo số lượng phân đoạn, bởi vì toàn bộ tập hợp con phân đoạn phải được tải.