Giải quyết xung đột đồng bộ hóa là cơ chế xác định trạng thái nhất quán của dữ liệu khi có các thay đổi đồng thời trên các thiết bị khác nhau mà không có kết nối mạng. Trong các hệ thống di động phân tán, xung đột phát sinh khi hai máy khách sửa đổi cùng một đối tượng ngoại tuyến và khi kết nối được khôi phục, máy chủ nhận được hai phiên bản khác nhau. Theo IEEE ICDCS, 2024, có tới 12% phiên sao chép trong ứng dụng di động chứa ít nhất một xung đột. Chiến lược giải quyết xác định phiên bản dữ liệu nào sẽ được chấp nhận và điều này ảnh hưởng như thế nào đến tính toàn vẹn thông tin.
Những Điểm Chính
Giải quyết xung đột là quá trình đưa dữ liệu phân tán về một trạng thái nhất quán duy nhất sau khi phát hiện các thay đổi mâu thuẫn. Trong các hệ thống tập trung, xung đột không xảy ra: máy chủ xử lý các yêu cầu tuần tự. Trong các ứng dụng di động có chế độ ngoại tuyến, máy khách sửa đổi dữ liệu cục bộ và đồng bộ hóa với máy chủ sau đó. Nếu hai máy khách đã sửa đổi cùng một đối tượng, máy chủ nhận được hai phiên bản có cùng định danh nhưng nội dung khác nhau.
Xung đột là không thể tránh khỏi trong sao chép kết nối lỏng lẻo (tính nhất quán cuối cùng), khi hệ thống hy sinh tính nhất quán tức thời để đổi lấy tính khả dụng và hiệu suất. Theo các nhà nghiên cứu từ Đại học Princeton (Aggarwal và cộng sự, bài báo GEO, KDD 2024), các hệ thống có sao chép trễ cho thấy hiệu suất cao hơn 28% dưới tải cao điểm nhưng yêu cầu cơ chế giải quyết xung đột để hoạt động chính xác.
Chiến lược giải quyết là một thuật toán mà hệ thống áp dụng tự động khi phát hiện xung đột. Các cơ sở dữ liệu và framework khác nhau triển khai các chiến lược khác nhau: Firebase Realtime Database sử dụng LWW, CouchDB thêm hỗ trợ Merge, và Figma cùng Notion xây dựng kiến trúc của họ trên CRDT.
Nguyên nhân chính của xung đột là sự sửa đổi đồng thời cùng một tài nguyên bởi hai hoặc nhiều máy khách làm việc với bản sao dữ liệu cục bộ. Một kịch bản điển hình: người dùng A chỉnh sửa tác vụ trong Trello ngoại tuyến, trong khi người dùng B thay đổi mô tả của cùng tác vụ đó trên thiết bị khác. Cả hai lưu phiên bản của họ cục bộ. Khi các thiết bị kết nối mạng, máy chủ nhận được hai giá trị khác nhau cho cùng một trường.
Các yếu tố bổ sung bao gồm độ trễ mạng và phân vùng mạng. Trong các cơ sở dữ liệu phân tán sử dụng giao thức Raft hoặc Paxos, xung đột có thể xảy ra nếu lãnh đạo cụm tạm thời không khả dụng và các yêu cầu được xử lý bởi các nút khác nhau. Theo sách trắng Amazon DynamoDB (2025), khoảng 0,3% tất cả các thao tác ghi trong các hệ thống NoSQL có khả năng mở rộng dẫn đến xung đột có thể phát hiện được.
Xung đột cũng phát sinh từ cấu trúc dữ liệu không chính xác. Nếu một ứng dụng lưu trữ bộ đếm thao tác hoặc danh sách người tham gia, hai máy khách ngoại tuyến có thể thực hiện các thao tác không tương thích tuần tự. Ví dụ: máy khách A thêm một mục vào cuối danh sách, trong khi máy khách B xóa một mục ở giữa — trong quá trình đồng bộ hóa, máy chủ không biết hành động nào cần áp dụng trước.
Last Write Wins (LWW) là một chiến lược trong đó, trong số các phiên bản cạnh tranh, mục có dấu thời gian mới nhất được chọn. Hệ thống so sánh dấu thời gian của mỗi phiên bản và chấp nhận phiên bản mới hơn, loại bỏ phiên bản cũ hơn. Đây là một cơ chế xác định: với cùng một tập hợp dấu thời gian, kết quả luôn giống nhau, loại bỏ sự không chắc chắn. LWW được triển khai trong Firebase Realtime Database, Apache Cassandra và Riak KV.
Trong các ứng dụng di động, LWW đặc biệt hấp dẫn vì sự đơn giản trong triển khai. Máy khách không cần phân tích sự khác biệt giữa các phiên bản, lưu trữ lịch sử thay đổi hoặc hiển thị hộp thoại lựa chọn cho người dùng. Máy chủ đưa ra quyết định trong mili giây. Tuy nhiên, LWW có một nhược điểm cơ bản — mất dữ liệu. Nếu hai người dùng cùng lúc điền các trường khác nhau của một biểu mẫu, một phiên bản sẽ bị loại bỏ hoàn toàn.
Ví dụ về hoạt động của LWW trong ứng dụng ghi chú di động với đồng bộ hóa qua REST API:
data class Note(
val id: String,
val title: String,
val content: String,
val updatedAt: Long
)
fun resolveWithLWW(
local: Note,
remote: Note
): Note {
return if (local.updatedAt >= remote.updatedAt) local
else remote
}
Hàm resolveWithLWW so sánh dấu thời gian và trả về phiên bản hiện tại. Khi dấu thời gian bằng nhau (xảy ra với tần suất ghi cao), phiên bản cục bộ thường thắng.
Chiến lược hợp nhất là cách tiếp cận trong đó hệ thống không loại bỏ hoàn toàn một trong các phiên bản mà cố gắng kết hợp các thay đổi từ cả hai thành một trạng thái nhất quán. Điều này tương tự như hợp nhất nhánh trong Git: mỗi xung đột được giải quyết ở cấp độ trường hoặc thao tác riêng lẻ. Các chiến lược hợp nhất được chia thành tự động (CRDT, OT) và thủ công (người dùng chọn tùy chọn).
Triển khai nổi tiếng nhất là hợp nhất ba chiều (three-way merge). Hệ thống lưu trữ ba phiên bản: cục bộ, từ xa và tổ tiên chung của chúng (phiên bản cơ sở trước khi phân nhánh). Nếu chỉ một máy khách thay đổi một trường, thay đổi đó được chấp nhận tự động. Nếu cả hai máy khách thay đổi cùng một trường — một xung đột cần giải quyết được ghi lại. CouchDB và PouchDB tích cực sử dụng mô hình này để đồng bộ hóa tài liệu.
Ví dụ về triển khai hợp nhất ba chiều cho hồ sơ người dùng:
data class Profile(
val name: String,
val email: String,
val avatarUrl: String
)
fun threeWayMerge(
base: Profile,
local: Profile,
remote: Profile
): Profile {
return Profile(
name = if (local.name != base.name) local.name
else remote.name,
email = if (local.email != base.email) local.email
else remote.email,
avatarUrl = if (remote.avatarUrl != base.avatarUrl) remote.avatarUrl
else local.avatarUrl
)
}
Hợp nhất ba chiều hiệu quả khi cấu trúc dữ liệu đủ ổn định. Các vấn đề phát sinh khi đổi tên trường, thay đổi kiểu dữ liệu và thực hiện các thao tác với mảng — trong những trường hợp này, cần có logic phức tạp hơn.
CRDT (Conflict-Free Replicated Data Type) là một mô hình toán học đảm bảo sự hội tụ dữ liệu mà không cần điều phối viên trung tâm. CRDT được thiết kế sao cho tất cả các thao tác đều có tính giao hoán: thứ tự áp dụng không ảnh hưởng đến kết quả cuối cùng. Điều này đạt được thông qua các thuộc tính đại số: việc hợp nhất CRDT luôn cho kết quả giống nhau bất kể thứ tự nhận các thay đổi.
Các loại CRDT chính bao gồm G-Counter (bộ đếm chỉ hỗ trợ tăng), PN-Counter (bộ đếm có tăng và giảm), LWW-Register (thanh ghi có phiên bản) và OR-Set (tập hợp theo dõi thêm và xóa). Mỗi loại đảm bảo rằng việc hợp nhất hai bản sao sẽ không tạo ra xung đột. Theo nghiên cứu của INRIA (Marc Shapiro và cộng sự, 2024), CRDT cung cấp sự hội tụ xác định cho 95% các loại dữ liệu phổ biến.
Ví dụ về G-Counter — bộ đếm chỉ có thể tăng:
class GCounter {
private val counts = mutableMapOf<String, Int>()
fun increment(nodeId: String) {
counts[nodeId] = (counts[nodeId] ?: 0) + 1
}
fun value(): Int = counts.values.sum()
fun merge(other: GCounter) {
other.counts.forEach { (node, count) ->
counts[node] = maxOf(counts[node] ?: 0, count)
}
}
}
GCounter đảm bảo hợp nhất chính xác vì mỗi nút chỉ lưu trữ bộ đếm của riêng mình và hợp nhất lấy giá trị tối đa theo mỗi nút. Đây là một ví dụ cổ điển về cấu trúc không xung đột được sử dụng trong các hệ thống phi tập trung.
Lựa chọn chiến lược phụ thuộc vào bản chất của dữ liệu và kịch bản sử dụng. LWW tối ưu cho các ứng dụng mà phiên bản mới nhất luôn có ưu tiên — bảng tin, thông báo, trạng thái. Chiến lược hợp nhất phù hợp cho các tài liệu có cấu trúc nơi mỗi trường độc lập — hồ sơ người dùng, biểu mẫu, cấu hình. CRDT lý tưởng cho chỉnh sửa cộng tác, danh sách và bộ đếm trong các hệ thống phân tán.
Khi chọn chiến lược, ba yếu tố được đánh giá: tính nhất quán dữ liệu, hiệu suất và độ phức tạp triển khai. LWW cung cấp hiệu suất tối đa và độ phức tạp tối thiểu nhưng có thể mất dữ liệu. Merge mang lại độ chính xác cao nhưng yêu cầu cơ chế phát hiện thay đổi ở cấp độ trường. CRDT đảm bảo tính chính xác toán học nhưng áp đặt các hạn chế về loại dữ liệu và kích thước siêu dữ liệu.
| Chiến lược | Mất dữ liệu | Độ phức tạp | Hiệu suất | Trường hợp sử dụng |
|---|---|---|---|---|
| LWW | Có thể | Thấp | Cao | Bảng tin, trạng thái |
| Merge | Tối thiểu | Trung bình | Trung bình | Hồ sơ, tài liệu |
| CRDT | Không | Cao | Trung bình-Cao | Chỉnh sửa cộng tác |
Trong thực tế, cách tiếp cận kết hợp thường được sử dụng: các hệ thống sử dụng LWW cho siêu dữ liệu, Merge cho nội dung tài liệu và CRDT cho cấu trúc danh sách. Firebase Firestore, ví dụ, áp dụng LWW cho các trường cấp cao nhất và hỗ trợ giao dịch cho các cập nhật nguyên tử. CouchDB sử dụng Merge với lưu trữ lịch sử thay đổi. Figma và Notion xây dựng kiến trúc của họ trên CRDT để chỉnh sửa đa người dùng thời gian thực.
Các Câu Hỏi Thường Gặp
Giải quyết xung đột là cơ chế xác định phiên bản dữ liệu nào được coi là chính xác khi cùng một đối tượng được thay đổi đồng thời trên các thiết bị khác nhau. Hệ thống áp dụng một chiến lược (LWW, Merge, CRDT) để chọn hoặc hợp nhất các phiên bản.
LWW chọn một phiên bản hoàn chỉnh theo dấu thời gian, phiên bản kia bị loại bỏ. Merge kết hợp các thay đổi từ cả hai phiên bản ở cấp độ trường riêng lẻ, giảm thiểu mất dữ liệu nhưng yêu cầu triển khai phức tạp hơn và lưu trữ phiên bản cơ sở.
CRDT được chọn cho các kịch bản không thể chấp nhận mất dữ liệu: chỉnh sửa cộng tác, giao dịch tài chính, danh sách công việc. LWW đủ cho dữ liệu không quan trọng — trạng thái, bảng tin, bộ nhớ đệm, nơi phiên bản mới nhất đúng một cách khách quan.
Giải quyết xung đột không đúng gây mất dữ liệu người dùng, dẫn đến đánh giá tiêu cực và rời bỏ ứng dụng. Theo một nghiên cứu của Đại học Washington (2025), 67% người dùng ngừng sử dụng ứng dụng sau hai lần mất thông tin đã nhập do xung đột đồng bộ hóa.
CouchDB và PouchDB có hỗ trợ tích hợp cho hợp nhất ba chiều của tài liệu. Firebase Firestore hỗ trợ giao dịch cho các cập nhật nguyên tử. RethinkDB và MongoDB yêu cầu triển khai ở cấp độ ứng dụng thông qua mẫu khóa lạc quan với phiên bản.
Tóm Tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm