NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Cho đến gần đây, Odnoklassniki đã lưu trữ khoảng 50 TB dữ liệu được xử lý theo thời gian thực trong SQL Server. Đối với khối lượng như vậy, hầu như không thể cung cấp quyền truy cập nhanh chóng và đáng tin cậy, thậm chí không thể chịu được lỗi của trung tâm dữ liệu bằng cách sử dụng SQL DBMS. Thông thường, trong những trường hợp như vậy, một trong các kho lưu trữ NoSQL được sử dụng, nhưng không phải mọi thứ đều có thể được chuyển sang NoSQL: một số thực thể yêu cầu đảm bảo giao dịch ACID.

Điều này dẫn chúng tôi đến việc sử dụng bộ lưu trữ NewSQL, tức là một DBMS cung cấp khả năng chịu lỗi, khả năng mở rộng và hiệu suất của các hệ thống NoSQL, nhưng đồng thời duy trì các đảm bảo ACID quen thuộc với các hệ thống cổ điển. Có rất ít hệ thống công nghiệp thuộc loại mới này hoạt động được, vì vậy chúng tôi đã tự mình triển khai hệ thống như vậy và đưa nó vào vận hành thương mại.

Nó hoạt động như thế nào và điều gì đã xảy ra - hãy đọc phần cắt.

Ngày nay, lượng khán giả hàng tháng của Odnoklassniki là hơn 70 triệu lượt người truy cập. Chúng tôi Chúng tôi nằm trong top XNUMX mạng xã hội lớn nhất thế giới và nằm trong số 8000 trang web mà người dùng dành nhiều thời gian nhất. Cơ sở hạ tầng OK xử lý tải rất cao: hơn một triệu yêu cầu HTTP/giây trên mỗi mặt trận. Các bộ phận của nhóm máy chủ gồm hơn 1 chiếc được đặt gần nhau - trong bốn trung tâm dữ liệu ở Moscow, điều này cho phép độ trễ mạng giữa chúng dưới XNUMX ms.

Chúng tôi đã sử dụng Cassandra từ năm 2010, bắt đầu từ phiên bản 0.6. Ngày nay có vài chục cụm đang hoạt động. Cụm nhanh nhất xử lý hơn 4 triệu thao tác mỗi giây và cụm lưu trữ lớn nhất có dung lượng 260 TB.

Tuy nhiên đây đều là những cụm NoSQL thông thường dùng để lưu trữ phối hợp yếu dữ liệu. Chúng tôi muốn thay thế bộ lưu trữ nhất quán chính, Microsoft SQL Server, đã được sử dụng kể từ khi thành lập Odnoklassniki. Bộ lưu trữ bao gồm hơn 300 máy SQL Server Standard Edition, chứa 50 TB dữ liệu - các thực thể kinh doanh. Dữ liệu này được sửa đổi như một phần của giao dịch ACID và yêu cầu tính nhất quán cao.

Để phân phối dữ liệu trên các nút SQL Server, chúng tôi đã sử dụng cả chiều dọc và chiều ngang phân vùng (phân mảnh). Trước đây, chúng tôi đã sử dụng sơ đồ phân chia dữ liệu đơn giản: mỗi thực thể được liên kết với một mã thông báo - một chức năng của ID thực thể. Các thực thể có cùng mã thông báo được đặt trên cùng một máy chủ SQL. Mối quan hệ chi tiết-chính được triển khai sao cho mã thông báo của bản ghi chính và bản ghi con luôn khớp với nhau và được đặt trên cùng một máy chủ. Trong mạng xã hội, hầu hết tất cả các bản ghi đều được tạo thay mặt người dùng - có nghĩa là tất cả dữ liệu người dùng trong một hệ thống con chức năng được lưu trữ trên một máy chủ. Nghĩa là, một giao dịch kinh doanh hầu như luôn bao gồm các bảng từ một máy chủ SQL, điều này giúp đảm bảo tính nhất quán của dữ liệu bằng cách sử dụng các giao dịch ACID cục bộ mà không cần sử dụng chậm và không đáng tin cậy giao dịch ACID phân tán.

Nhờ sharding và tăng tốc SQL:

  • Chúng tôi không sử dụng các ràng buộc khóa ngoài vì khi phân chia ID thực thể có thể nằm trên một máy chủ khác.
  • Chúng tôi không sử dụng các thủ tục và trình kích hoạt được lưu trữ do tải bổ sung trên CPU DBMS.
  • Chúng tôi không sử dụng THAM GIA vì tất cả những điều trên và rất nhiều lần đọc ngẫu nhiên từ đĩa.
  • Ngoài giao dịch, chúng tôi sử dụng mức cách ly Đọc không được cam kết để giảm tình trạng bế tắc.
  • Chúng tôi chỉ thực hiện các giao dịch ngắn (trung bình ngắn hơn 100 ms).
  • Chúng tôi không sử dụng CẬP NHẬT và XÓA nhiều hàng do số lượng bế tắc lớn - chúng tôi chỉ cập nhật một bản ghi tại một thời điểm.
  • Chúng tôi luôn chỉ thực hiện các truy vấn trên các chỉ mục - một truy vấn có kế hoạch quét toàn bộ bảng đối với chúng tôi có nghĩa là làm quá tải cơ sở dữ liệu và khiến cơ sở dữ liệu bị lỗi.

Các bước này cho phép chúng tôi đạt được hiệu suất gần như tối đa từ máy chủ SQL. Tuy nhiên, các vấn đề ngày càng trở nên nhiều hơn. Hãy nhìn vào chúng.

Sự cố với SQL

  • Vì chúng tôi sử dụng phân đoạn tự viết nên việc thêm phân đoạn mới được quản trị viên thực hiện thủ công. Trong suốt thời gian qua, các bản sao dữ liệu có thể mở rộng không đáp ứng được yêu cầu phục vụ.
  • Khi số lượng bản ghi trong bảng tăng lên, tốc độ chèn và sửa đổi giảm; khi thêm chỉ mục vào bảng hiện có, tốc độ giảm theo hệ số; việc tạo và tạo lại chỉ mục xảy ra theo thời gian chết.
  • Việc có một lượng nhỏ Windows cho SQL Server trong sản xuất khiến việc quản lý cơ sở hạ tầng gặp khó khăn

Nhưng vấn đề chính là

khả năng chịu lỗi

Máy chủ SQL cổ điển có khả năng chịu lỗi kém. Giả sử bạn chỉ có một máy chủ cơ sở dữ liệu và nó bị lỗi ba năm một lần. Trong thời gian này, trang web ngừng hoạt động trong 20 phút, điều này có thể chấp nhận được. Nếu bạn có 64 máy chủ thì trang web sẽ ngừng hoạt động ba tuần một lần. Và nếu bạn có 200 máy chủ thì trang web đó không hoạt động hàng tuần. Đây là vấn đề.

Có thể làm gì để cải thiện khả năng chịu lỗi của máy chủ SQL? Wikipedia mời chúng ta xây dựng cụm có tính sẵn sàng cao: trong trường hợp bất kỳ thành phần nào bị lỗi thì sẽ có một thành phần dự phòng.

Điều này đòi hỏi một nhóm thiết bị đắt tiền: nhiều thiết bị sao chép, cáp quang, bộ lưu trữ dùng chung và việc đưa vào nguồn dự trữ không hoạt động đáng tin cậy: khoảng 10% số lần chuyển mạch kết thúc bằng sự cố của nút dự phòng giống như một đoàn tàu chạy phía sau nút chính.

Nhưng nhược điểm chính của cụm có tính sẵn sàng cao như vậy là tính sẵn sàng bằng XNUMX nếu trung tâm dữ liệu nơi đặt cụm đó bị lỗi. Odnoklassniki có bốn trung tâm dữ liệu và chúng tôi cần đảm bảo hoạt động trong trường hợp một trong số đó bị lỗi hoàn toàn.

Đối với điều này chúng ta có thể sử dụng Đa chủ bản sao được tích hợp vào SQL Server. Giải pháp này đắt hơn nhiều do chi phí phần mềm và gặp phải các vấn đề phổ biến về sao chép - độ trễ giao dịch không thể đoán trước với sao chép đồng bộ và độ trễ trong việc áp dụng sao chép (và kết quả là mất sửa đổi) với sao chép không đồng bộ. Điều ngụ ý giải quyết xung đột thủ công làm cho tùy chọn này hoàn toàn không thể áp dụng được đối với chúng tôi.

Tất cả những vấn đề này đòi hỏi một giải pháp triệt để và chúng tôi bắt đầu phân tích chúng một cách chi tiết. Ở đây chúng ta cần làm quen với những gì SQL Server chủ yếu thực hiện - giao dịch.

Giao dịch đơn giản

Hãy xem xét giao dịch đơn giản nhất, từ quan điểm của một lập trình viên SQL ứng dụng: thêm ảnh vào album. Album và ảnh được lưu trữ trong các tấm khác nhau. Album có một quầy ảnh công cộng. Sau đó, một giao dịch như vậy được chia thành các bước sau:

  1. Chúng tôi khóa album bằng chìa khóa.
  2. Tạo một mục trong bảng ảnh.
  3. Nếu ảnh có trạng thái công khai thì hãy thêm bộ đếm ảnh công khai vào album, cập nhật bản ghi và thực hiện giao dịch.

Hoặc trong mã giả:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

Chúng tôi thấy rằng kịch bản phổ biến nhất đối với giao dịch kinh doanh là đọc dữ liệu từ cơ sở dữ liệu vào bộ nhớ của máy chủ ứng dụng, thay đổi nội dung nào đó và lưu các giá trị mới trở lại cơ sở dữ liệu. Thông thường trong một giao dịch như vậy, chúng tôi cập nhật một số thực thể, một số bảng.

Khi thực hiện một giao dịch, việc sửa đổi đồng thời cùng một dữ liệu từ hệ thống khác có thể xảy ra. Ví dụ: Antispam có thể quyết định rằng người dùng đang nghi ngờ bằng cách nào đó và do đó tất cả ảnh của người dùng sẽ không còn được công khai nữa, chúng cần được gửi để kiểm duyệt, nghĩa là thay đổi photo.status thành một số giá trị khác và tắt bộ đếm tương ứng. Rõ ràng, nếu hoạt động này xảy ra mà không có sự đảm bảo về tính nguyên tử của ứng dụng và sự cô lập của các sửa đổi cạnh tranh, như trong ACID, thì kết quả sẽ không như những gì cần thiết - hoặc bộ đếm ảnh sẽ hiển thị sai giá trị hoặc không phải tất cả ảnh sẽ được gửi để kiểm duyệt.

Rất nhiều mã tương tự, thao túng các thực thể kinh doanh khác nhau trong một giao dịch, đã được viết trong suốt quá trình tồn tại của Odnoklassniki. Dựa trên kinh nghiệm di chuyển sang NoSQL từ Tính nhất quán cuối cùng Chúng tôi biết rằng thách thức lớn nhất (và đầu tư thời gian) đến từ việc phát triển mã để duy trì tính nhất quán của dữ liệu. Do đó, chúng tôi coi yêu cầu chính đối với bộ lưu trữ mới là cung cấp các giao dịch ACID thực cho logic ứng dụng.

Các yêu cầu khác, không kém phần quan trọng, là:

  • Nếu trung tâm dữ liệu bị lỗi, cả việc đọc và ghi vào bộ lưu trữ mới đều phải khả dụng.
  • Duy trì tốc độ phát triển hiện tại. Nghĩa là, khi làm việc với một kho lưu trữ mới, số lượng mã phải gần như nhau; không cần thêm bất cứ thứ gì vào kho lưu trữ, không cần phát triển các thuật toán để giải quyết xung đột, duy trì các chỉ mục phụ, v.v.
  • Tốc độ của bộ lưu trữ mới phải khá cao, cả khi đọc dữ liệu và khi xử lý các giao dịch, điều đó có nghĩa là các giải pháp nghiêm ngặt về mặt học thuật, phổ quát nhưng chậm, chẳng hạn như không thể áp dụng được cam kết hai giai đoạn.
  • Tự động mở rộng quy mô khi đang di chuyển.
  • Sử dụng máy chủ giá rẻ thông thường mà không cần phải mua phần cứng ngoại lai.
  • Khả năng phát triển lưu trữ của các nhà phát triển công ty. Nói cách khác, ưu tiên được dành cho các giải pháp độc quyền hoặc nguồn mở, tốt nhất là bằng Java.

Quyết định, quyết định

Phân tích các giải pháp khả thi, chúng tôi đi đến hai lựa chọn kiến ​​trúc khả thi:

Đầu tiên là sử dụng bất kỳ máy chủ SQL nào và triển khai khả năng chịu lỗi, cơ chế mở rộng quy mô, cụm chuyển đổi dự phòng, giải quyết xung đột và các giao dịch ACID phân tán, đáng tin cậy và nhanh chóng. Chúng tôi đánh giá tùy chọn này là rất không tầm thường và tốn nhiều công sức.

Tùy chọn thứ hai là sử dụng bộ lưu trữ NoSQL làm sẵn với khả năng mở rộng quy mô được triển khai, cụm chuyển đổi dự phòng, giải quyết xung đột và tự triển khai các giao dịch cũng như SQL. Thoạt nhìn, ngay cả nhiệm vụ triển khai SQL, chưa kể đến các giao dịch ACID, có vẻ như sẽ mất nhiều năm. Nhưng sau đó, chúng tôi nhận ra rằng bộ tính năng SQL mà chúng tôi sử dụng trong thực tế khác xa so với ANSI SQL. Cassandra CQL khác xa với ANSI SQL. Xem xét kỹ hơn về CQL, chúng tôi nhận ra rằng nó khá gần với những gì chúng tôi cần.

Cassandra và CQL

Vậy Cassandra có gì thú vị, nó có những khả năng gì?

Đầu tiên, tại đây bạn có thể tạo các bảng hỗ trợ nhiều loại dữ liệu khác nhau; bạn có thể thực hiện CHỌN hoặc CẬP NHẬT trên khóa chính.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

Để đảm bảo tính nhất quán của dữ liệu bản sao, Cassandra sử dụng cách tiếp cận đại biểu. Trong trường hợp đơn giản nhất, điều này có nghĩa là khi ba bản sao của cùng một hàng được đặt trên các nút khác nhau của cụm, việc ghi được coi là thành công nếu phần lớn các nút (nghĩa là hai trong số ba nút) xác nhận thành công của thao tác ghi này. . Dữ liệu hàng được coi là nhất quán nếu khi đọc, phần lớn các nút đã được thăm dò và xác nhận chúng. Do đó, với ba bản sao, tính nhất quán của dữ liệu đầy đủ và tức thời được đảm bảo nếu một nút bị lỗi. Cách tiếp cận này cho phép chúng tôi triển khai một sơ đồ thậm chí còn đáng tin cậy hơn: luôn gửi yêu cầu đến cả ba bản sao, chờ phản hồi từ hai bản sao nhanh nhất. Phản hồi muộn của bản sao thứ ba sẽ bị loại bỏ trong trường hợp này. Nút phản hồi chậm có thể gặp sự cố nghiêm trọng - phanh, thu gom rác trong JVM, lấy lại bộ nhớ trực tiếp trong nhân Linux, lỗi phần cứng, ngắt kết nối mạng. Tuy nhiên, điều này không ảnh hưởng đến hoạt động hoặc dữ liệu của khách hàng dưới bất kỳ hình thức nào.

Cách tiếp cận khi chúng tôi liên hệ với ba nút và nhận được phản hồi từ hai nút được gọi là suy đoán: yêu cầu về các bản sao bổ sung được gửi ngay cả trước khi nó “rơi ra”.

Một lợi ích khác của Cassandra là Batchlog, một cơ chế đảm bảo rằng một loạt thay đổi bạn thực hiện sẽ được áp dụng đầy đủ hoặc hoàn toàn không được áp dụng. Điều này cho phép chúng ta giải A bằng ACID - tính nguyên tử ngay lập tức.

Điều gần gũi nhất với các giao dịch trong Cassandra là cái gọi là “giao dịch nhẹ". Nhưng chúng khác xa với các giao dịch ACID “thực sự”: trên thực tế, đây là cơ hội để thực hiện CAS trên dữ liệu chỉ từ một bản ghi, sử dụng sự đồng thuận bằng giao thức Paxos hạng nặng. Vì vậy, tốc độ của các giao dịch như vậy là thấp.

Những gì chúng tôi đã thiếu ở Cassandra

Vì vậy, chúng tôi phải triển khai các giao dịch ACID thực sự trong Cassandra. Bằng cách sử dụng tính năng này, chúng tôi có thể dễ dàng triển khai hai tính năng tiện lợi khác của DBMS cổ điển: các chỉ mục nhanh nhất quán, cho phép chúng tôi thực hiện các lựa chọn dữ liệu không chỉ bằng khóa chính và một trình tạo ID tăng tự động đơn điệu thông thường.

C*Một

Do đó một DBMS mới đã ra đời C*Một, bao gồm ba loại nút máy chủ:

  • Lưu trữ – (gần như) máy chủ Cassandra tiêu chuẩn chịu trách nhiệm lưu trữ dữ liệu trên đĩa cục bộ. Khi tải và khối lượng dữ liệu tăng lên, số lượng của chúng có thể dễ dàng tăng lên hàng chục và hàng trăm.
  • Điều phối viên giao dịch - đảm bảo việc thực hiện các giao dịch.
  • Khách hàng là các máy chủ ứng dụng thực hiện các hoạt động kinh doanh và bắt đầu các giao dịch. Có thể có hàng ngàn khách hàng như vậy.

NewSQL = NoSQL+ACID

Tất cả các loại máy chủ đều là một phần của một cụm chung, sử dụng giao thức tin nhắn Cassandra nội bộ để liên lạc với nhau và tin đồn để trao đổi thông tin cụm. Với Heartbeat, các máy chủ tìm hiểu về các lỗi chung, duy trì một lược đồ dữ liệu duy nhất - các bảng, cấu trúc và bản sao của chúng; sơ đồ phân vùng, cấu trúc liên kết cụm, v.v.

Khách hàng

NewSQL = NoSQL+ACID

Thay vì trình điều khiển tiêu chuẩn, chế độ Fat Client được sử dụng. Nút như vậy không lưu trữ dữ liệu nhưng có thể đóng vai trò là người điều phối để thực hiện yêu cầu, nghĩa là chính Máy khách đóng vai trò là người điều phối các yêu cầu của mình: nó truy vấn các bản sao lưu trữ và giải quyết xung đột. Điều này không chỉ đáng tin cậy hơn và nhanh hơn trình điều khiển tiêu chuẩn vốn yêu cầu liên lạc với điều phối viên từ xa mà còn cho phép bạn kiểm soát việc truyền yêu cầu. Ngoài giao dịch mở trên máy khách, các yêu cầu sẽ được gửi đến kho lưu trữ. Nếu khách hàng đã mở một giao dịch thì tất cả các yêu cầu trong giao dịch sẽ được gửi đến điều phối viên giao dịch.
NewSQL = NoSQL+ACID

Điều phối viên giao dịch C*One

Điều phối viên là thứ chúng tôi đã triển khai cho C*One ngay từ đầu. Nó chịu trách nhiệm quản lý các giao dịch, khóa và thứ tự áp dụng các giao dịch.

Đối với mỗi giao dịch được phục vụ, điều phối viên tạo dấu thời gian: mỗi giao dịch tiếp theo lớn hơn giao dịch trước đó. Do hệ thống giải quyết xung đột của Cassandra dựa trên dấu thời gian (trong số hai bản ghi xung đột, bản ghi có dấu thời gian mới nhất được coi là hiện tại), xung đột sẽ luôn được giải quyết theo hướng có lợi cho giao dịch tiếp theo. Như vậy chúng tôi đã triển khai đồng hồ lamport - một cách rẻ tiền để giải quyết xung đột trong hệ thống phân tán.

Ổ khóa

Để đảm bảo sự cô lập, chúng tôi quyết định sử dụng phương pháp đơn giản nhất - khóa bi quan dựa trên khóa chính của bản ghi. Nói cách khác, trong một giao dịch, bản ghi trước tiên phải được khóa, sau đó mới được đọc, sửa đổi và lưu lại. Chỉ sau khi cam kết thành công, một bản ghi mới có thể được mở khóa để các giao dịch cạnh tranh có thể sử dụng nó.

Việc thực hiện khóa như vậy rất đơn giản trong môi trường không phân tán. Trong hệ thống phân tán, có hai tùy chọn chính: triển khai khóa phân tán trên cụm hoặc phân phối các giao dịch để các giao dịch liên quan đến cùng một bản ghi luôn được phục vụ bởi cùng một điều phối viên.

Vì trong trường hợp của chúng tôi, dữ liệu đã được phân phối giữa các nhóm giao dịch cục bộ trong SQL nên đã quyết định chỉ định các nhóm giao dịch cục bộ cho người điều phối: một điều phối viên thực hiện tất cả các giao dịch với mã thông báo từ 0 đến 9, nhóm thứ hai - với mã thông báo từ 10 đến 19, và như thế. Kết quả là, mỗi phiên bản điều phối viên trở thành chủ của nhóm giao dịch.

Sau đó, các khóa có thể được triển khai dưới dạng HashMap tầm thường trong bộ nhớ của điều phối viên.

Lỗi điều phối viên

Vì một điều phối viên chỉ phục vụ một nhóm giao dịch, điều rất quan trọng là phải nhanh chóng xác định thực tế lỗi của nó để lần thử thứ hai thực hiện giao dịch sẽ hết thời gian. Để thực hiện việc này nhanh chóng và đáng tin cậy, chúng tôi đã sử dụng giao thức nhịp tim đại biểu được kết nối đầy đủ:

Mỗi trung tâm dữ liệu lưu trữ ít nhất hai nút điều phối. Theo định kỳ, mỗi điều phối viên sẽ gửi một tin nhắn nhịp tim đến những điều phối viên khác và thông báo cho họ về chức năng của nó, cũng như những tin nhắn nhịp tim mà nó nhận được từ điều phối viên nào trong cụm lần trước.

NewSQL = NoSQL+ACID

Nhận thông tin tương tự từ những người khác như một phần của thông điệp nhịp tim của họ, mỗi điều phối viên tự quyết định nút cụm nào đang hoạt động và nút nào không, được hướng dẫn bởi nguyên tắc đại biểu: nếu nút X đã nhận được thông tin từ phần lớn các nút trong cụm về trạng thái bình thường nhận được tin nhắn từ nút Y thì Y hoạt động. Và ngược lại, ngay khi đa số báo cáo thiếu tin nhắn từ nút Y thì Y đã từ chối. Điều tò mò là nếu số đại biểu thông báo cho nút X rằng nó không còn nhận được tin nhắn từ nó nữa thì bản thân nút X sẽ tự coi mình đã thất bại.

Tin nhắn nhịp tim được gửi với tần suất cao, khoảng 20 lần mỗi giây, với khoảng thời gian 50 ms. Trong Java, rất khó để đảm bảo phản hồi của ứng dụng trong vòng 50 ms do độ dài tạm dừng tương đương do trình thu gom rác gây ra. Chúng tôi có thể đạt được thời gian phản hồi này bằng cách sử dụng trình thu thập rác G1, cho phép chúng tôi chỉ định mục tiêu trong khoảng thời gian tạm dừng GC. Tuy nhiên, đôi khi, khá hiếm khi bộ thu tạm dừng vượt quá 50 ms, điều này có thể dẫn đến phát hiện lỗi sai. Để ngăn điều này xảy ra, điều phối viên không báo cáo lỗi của nút từ xa khi thông báo nhịp tim đầu tiên từ nó biến mất, chỉ khi một số nút đã biến mất liên tiếp. Đây là cách chúng tôi quản lý để phát hiện lỗi của nút điều phối trong 200 bệnh đa xơ cứng.

Nhưng nó không đủ để nhanh chóng hiểu được nút nào đã ngừng hoạt động. Chúng ta cần phải làm gì đó về việc này.

Sự đặt chỗ

Kế hoạch cổ điển bao gồm, trong trường hợp thất bại tổng thể, bắt đầu một cuộc bầu cử mới bằng cách sử dụng một trong các hợp thời trang phổ quát thuật toán. Tuy nhiên, các thuật toán như vậy có những vấn đề nổi tiếng về sự hội tụ thời gian và độ dài của quá trình bầu cử. Chúng tôi có thể tránh được sự chậm trễ bổ sung như vậy bằng cách sử dụng sơ đồ thay thế điều phối viên trong mạng được kết nối đầy đủ:

NewSQL = NoSQL+ACID

Giả sử chúng ta muốn thực hiện một giao dịch trong nhóm 50. Hãy xác định trước sơ đồ thay thế, nghĩa là các nút nào sẽ thực hiện các giao dịch trong nhóm 50 trong trường hợp điều phối viên chính bị lỗi. Mục tiêu của chúng tôi là duy trì chức năng hệ thống trong trường hợp trung tâm dữ liệu bị lỗi. Hãy xác định rằng điểm dự trữ đầu tiên sẽ là nút từ một trung tâm dữ liệu khác và điểm dự trữ thứ hai sẽ là nút từ trung tâm dữ liệu thứ ba. Lược đồ này được chọn một lần và không thay đổi cho đến khi cấu trúc liên kết của cụm thay đổi, nghĩa là cho đến khi các nút mới nhập vào nó (điều này rất hiếm khi xảy ra). Quy trình chọn một bản chính đang hoạt động mới nếu bản chính cũ bị lỗi sẽ luôn như sau: bản dự trữ đầu tiên sẽ trở thành bản chính đang hoạt động và nếu nó ngừng hoạt động thì bản dự trữ thứ hai sẽ trở thành bản chính đang hoạt động.

Sơ đồ này đáng tin cậy hơn thuật toán phổ quát, vì để kích hoạt một bản gốc mới, chỉ cần xác định lỗi của bản gốc cũ là đủ.

Nhưng làm thế nào khách hàng có thể hiểu được chủ nhân nào đang làm việc? Không thể gửi thông tin tới hàng nghìn khách hàng trong 50 mili giây. Có thể xảy ra tình huống khách hàng gửi yêu cầu mở giao dịch mà chưa biết rằng chủ này không còn hoạt động và yêu cầu sẽ hết thời gian chờ. Để ngăn điều này xảy ra, khách hàng sẽ gửi yêu cầu mở giao dịch đến chủ nhóm và cả hai khoản dự trữ của anh ta cùng một lúc, nhưng chỉ người chủ hoạt động vào lúc này mới phản hồi yêu cầu này. Khách hàng sẽ chỉ thực hiện tất cả các giao tiếp tiếp theo trong giao dịch với chủ đang hoạt động.

Người sao lưu dự phòng đặt các yêu cầu giao dịch không phải của họ đã nhận được vào hàng đợi các giao dịch chưa sinh, nơi chúng được lưu trữ trong một thời gian. Nếu chủ hoạt động chết, chủ mới sẽ xử lý các yêu cầu mở giao dịch từ hàng đợi của nó và phản hồi cho khách hàng. Nếu khách hàng đã mở giao dịch với chủ cũ thì phản hồi thứ hai sẽ bị bỏ qua (và rõ ràng, giao dịch đó sẽ không hoàn thành và sẽ được khách hàng lặp lại).

Giao dịch hoạt động như thế nào

Giả sử một khách hàng đã gửi yêu cầu đến điều phối viên để mở một giao dịch cho một thực thể đó với một khóa chính như vậy. Điều phối viên khóa thực thể này và đặt nó vào bảng khóa trong bộ nhớ. Nếu cần, điều phối viên sẽ đọc thực thể này từ bộ lưu trữ và lưu trữ dữ liệu kết quả ở trạng thái giao dịch trong bộ nhớ của điều phối viên.

NewSQL = NoSQL+ACID

Khi khách hàng muốn thay đổi dữ liệu trong một giao dịch, nó sẽ gửi yêu cầu đến điều phối viên để sửa đổi thực thể và điều phối viên sẽ đặt dữ liệu mới vào bảng trạng thái giao dịch trong bộ nhớ. Việc này hoàn tất quá trình ghi - không có bản ghi nào được thực hiện vào bộ lưu trữ.

NewSQL = NoSQL+ACID

Khi khách hàng yêu cầu dữ liệu đã thay đổi của chính mình như một phần của giao dịch đang hoạt động, điều phối viên sẽ hành động như sau:

  • nếu ID đã có trong giao dịch thì dữ liệu sẽ được lấy từ bộ nhớ;
  • nếu ID không có trong bộ nhớ thì dữ liệu bị thiếu sẽ được đọc từ các nút lưu trữ, kết hợp với những dữ liệu đã có trong bộ nhớ và kết quả sẽ được cung cấp cho máy khách.

Do đó, máy khách có thể đọc các thay đổi của chính mình, nhưng các máy khách khác không nhìn thấy những thay đổi này vì chúng chỉ được lưu trữ trong bộ nhớ của điều phối viên; chúng chưa có trong các nút Cassandra.

NewSQL = NoSQL+ACID

Khi máy khách gửi cam kết, trạng thái trong bộ nhớ của dịch vụ sẽ được điều phối viên lưu trong một lô đã ghi và được gửi dưới dạng lô đã ghi đến bộ lưu trữ Cassandra. Các cửa hàng làm mọi thứ cần thiết để đảm bảo rằng gói này được áp dụng nguyên tử (hoàn toàn) và trả lại phản hồi cho điều phối viên, người sẽ mở khóa và xác nhận giao dịch thành công cho khách hàng.

NewSQL = NoSQL+ACID

Và để khôi phục, điều phối viên chỉ cần giải phóng bộ nhớ bị chiếm giữ bởi trạng thái giao dịch.

Nhờ những cải tiến trên, chúng tôi đã triển khai các nguyên tắc ACID:

  • Tính nguyên tử. Đây là sự đảm bảo rằng sẽ không có giao dịch nào được ghi lại một phần trong hệ thống; hoặc tất cả các hoạt động phụ của nó sẽ được hoàn thành hoặc không có giao dịch nào được hoàn thành. Chúng tôi tuân thủ nguyên tắc này thông qua lô được ghi lại trong Cassandra.
  • Tính nhất quán. Theo định nghĩa, mỗi giao dịch thành công chỉ ghi lại kết quả hợp lệ. Nếu sau khi mở giao dịch và thực hiện một phần thao tác, người ta phát hiện ra kết quả không hợp lệ thì việc khôi phục sẽ được thực hiện.
  • Sự cách ly. Khi một giao dịch được thực hiện, các giao dịch đồng thời sẽ không ảnh hưởng đến kết quả của nó. Các giao dịch cạnh tranh được cô lập bằng cách sử dụng các khóa bi quan trên điều phối viên. Đối với các lần đọc bên ngoài giao dịch, nguyên tắc cách ly được tuân thủ ở cấp độ Đã cam kết Đọc.
  • Tính ổn định. Bất kể sự cố ở cấp độ thấp hơn—mất hệ thống, lỗi phần cứng—các thay đổi được thực hiện bởi giao dịch đã hoàn thành thành công sẽ vẫn được giữ nguyên khi hoạt động tiếp tục.

Đọc theo chỉ mục

Hãy lấy một bảng đơn giản:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

Nó có ID (khóa chính), chủ sở hữu và ngày sửa đổi. Bạn cần thực hiện một yêu cầu rất đơn giản - chọn dữ liệu về chủ sở hữu có ngày thay đổi “cho ngày cuối cùng”.

SELECT *
WHERE owner=?
AND modified>?

Để truy vấn như vậy được xử lý nhanh chóng, trong SQL DBMS cổ điển, bạn cần xây dựng chỉ mục theo cột (chủ sở hữu, đã sửa đổi). Chúng ta có thể thực hiện điều này khá dễ dàng vì hiện tại chúng ta đã có đảm bảo về ACID!

Chỉ mục trong C*One

Có một bảng nguồn chứa các ảnh trong đó ID bản ghi là khóa chính.

NewSQL = NoSQL+ACID

Đối với một chỉ mục, C*One tạo một bảng mới là bản sao của bảng gốc. Khóa này giống với biểu thức chỉ mục và nó cũng bao gồm khóa chính của bản ghi từ bảng nguồn:

NewSQL = NoSQL+ACID

Giờ đây, truy vấn “chủ sở hữu cho ngày cuối cùng” có thể được viết lại dưới dạng lựa chọn từ một bảng khác:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

Tính nhất quán của dữ liệu trong ảnh bảng nguồn và bảng chỉ mục i1 được điều phối viên duy trì tự động. Chỉ dựa trên lược đồ dữ liệu, khi nhận được một thay đổi, điều phối viên sẽ tạo và lưu trữ thay đổi không chỉ trong bảng chính mà còn trong các bản sao. Không có hành động bổ sung nào được thực hiện trên bảng chỉ mục, nhật ký không được đọc và không có khóa nào được sử dụng. Nghĩa là, việc thêm chỉ mục hầu như không tiêu tốn tài nguyên và hầu như không ảnh hưởng đến tốc độ áp dụng các sửa đổi.

Bằng cách sử dụng ACID, chúng tôi có thể triển khai các chỉ mục giống như SQL. Chúng nhất quán, có thể mở rộng, nhanh chóng, có thể tổng hợp và được tích hợp vào ngôn ngữ truy vấn CQL. Không cần thay đổi mã ứng dụng để hỗ trợ các chỉ mục. Mọi thứ đều đơn giản như trong SQL. Và quan trọng nhất, các chỉ mục không ảnh hưởng đến tốc độ thực hiện các sửa đổi đối với bảng giao dịch ban đầu.

Chuyện gì đã xảy ra thế

Chúng tôi đã phát triển C*One ba năm trước và đưa nó vào hoạt động thương mại.

Cuối cùng chúng ta đã nhận được gì? Hãy đánh giá điều này bằng ví dụ về hệ thống con lưu trữ và xử lý ảnh, một trong những loại dữ liệu quan trọng nhất trong mạng xã hội. Chúng ta không nói về nội dung của các bức ảnh mà về tất cả các loại siêu thông tin. Hiện Odnoklassniki có khoảng 20 tỷ bản ghi như vậy, hệ thống xử lý 80 nghìn yêu cầu đọc mỗi giây, lên tới 8 nghìn giao dịch ACID mỗi giây liên quan đến sửa đổi dữ liệu.

Khi chúng tôi sử dụng SQL với hệ số sao chép = 1 (nhưng trong RAID 10), siêu thông tin ảnh được lưu trữ trên một cụm gồm 32 máy có tính sẵn sàng cao chạy Microsoft SQL Server (cộng với 11 bản sao lưu). 10 máy chủ cũng được phân bổ để lưu trữ các bản sao lưu. Tổng cộng có 50 chiếc xe đắt tiền. Đồng thời, hệ thống làm việc ở tải định mức, không dự trữ.

Sau khi di chuyển sang hệ thống mới, chúng tôi nhận được hệ số sao chép = 3 - một bản sao trong mỗi trung tâm dữ liệu. Hệ thống bao gồm 63 nút lưu trữ Cassandra và 6 máy điều phối, với tổng số 69 máy chủ. Nhưng những máy này rẻ hơn nhiều, tổng chi phí của chúng chỉ bằng khoảng 30% chi phí của một hệ thống SQL. Đồng thời, tải được giữ ở mức 30%.

Với sự ra đời của C*One, độ trễ cũng giảm: trong SQL, thao tác ghi mất khoảng 4,5 mili giây. Trong C*One - khoảng 1,6 ms. Thời lượng giao dịch trung bình dưới 40 ms, cam kết hoàn thành trong 2 ms, thời lượng đọc và ghi trung bình là 2 ms. Phân vị thứ 99 - chỉ 3-3,1 ms, số lần hết thời gian chờ đã giảm 100 lần - tất cả là do việc sử dụng rộng rãi sự đầu cơ.

Đến nay, hầu hết các nút SQL Server đã ngừng hoạt động; các sản phẩm mới chỉ được phát triển bằng C*One. Chúng tôi đã điều chỉnh C*One để hoạt động trên đám mây của mình một đám mây, giúp tăng tốc độ triển khai các cụm mới, đơn giản hóa cấu hình và tự động hóa hoạt động. Nếu không có mã nguồn thì việc làm này sẽ khó khăn và rườm rà hơn rất nhiều.

Bây giờ chúng tôi đang nỗ lực chuyển các phương tiện lưu trữ khác của mình sang đám mây - nhưng đó lại là một câu chuyện hoàn toàn khác.

Nguồn: www.habr.com

Thêm một lời nhận xét