Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Lịch sử hình thành VKontakte có trên Wikipedia, do chính Pavel kể lại. Có vẻ như mọi người đều đã biết đến cô ấy. Về nội bộ, kiến ​​trúc và cấu trúc của trang web trên HighLoad++ Pavel đã nói với tôi vào năm 2010. Nhiều máy chủ đã bị rò rỉ kể từ đó, vì vậy chúng tôi sẽ cập nhật thông tin: chúng tôi sẽ mổ xẻ nó, lấy ra bên trong, cân nó và xem xét thiết bị VK từ góc độ kỹ thuật.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Alexey Akulovich (AterCattus) nhà phát triển phụ trợ trong nhóm VKontakte. Bản ghi của báo cáo này là câu trả lời chung cho các câu hỏi thường gặp về hoạt động của nền tảng, cơ sở hạ tầng, máy chủ và sự tương tác giữa chúng, nhưng không phải về sự phát triển, cụ thể là về sắt. Riêng biệt, về cơ sở dữ liệu và thay vào đó, những gì VK có, về việc thu thập nhật ký và giám sát toàn bộ dự án. Chi tiết dưới vết cắt.



Trong hơn bốn năm, tôi đã giải quyết tất cả các loại nhiệm vụ liên quan đến phần phụ trợ.

  • Tải lên, lưu trữ, xử lý, phân phối phương tiện: video, phát trực tiếp, âm thanh, hình ảnh, tài liệu.
  • Cơ sở hạ tầng, nền tảng, giám sát nhà phát triển, nhật ký, bộ nhớ đệm khu vực, CDN, giao thức RPC độc quyền.
  • Tích hợp với các dịch vụ bên ngoài: thông báo đẩy, phân tích liên kết bên ngoài, nguồn cấp dữ liệu RSS.
  • Giúp đỡ đồng nghiệp với nhiều câu hỏi khác nhau, câu trả lời đòi hỏi phải đi sâu vào mã không xác định.

Trong thời gian này, tôi đã tham gia vào nhiều thành phần của trang web. Tôi muốn chia sẻ kinh nghiệm này.

Kiến trúc tổng thể

Mọi thứ, như thường lệ, bắt đầu với một máy chủ hoặc một nhóm máy chủ chấp nhận yêu cầu.

Máy chủ phía trước

Máy chủ phía trước chấp nhận các yêu cầu qua HTTPS, RTMP và WSS.

HTTPS - đây là những yêu cầu đối với phiên bản web chính và di động của trang web: vk.com và m.vk.com, cũng như các ứng dụng khách chính thức và không chính thức khác của API của chúng tôi: ứng dụng khách di động, trình nhắn tin. Chúng tôi có một buổi tiếp tân RTMP-lưu lượng truy cập cho chương trình phát sóng trực tiếp với các máy chủ phía trước riêng biệt và WSS- kết nối cho API phát trực tuyến.

Đối với HTTPS và WSS trên máy chủ, điều đó đáng giá nginx. Đối với các chương trình phát sóng RTMP, gần đây chúng tôi đã chuyển sang giải pháp của riêng mình động vật, nhưng nó nằm ngoài phạm vi của báo cáo. Để có khả năng chịu lỗi, các máy chủ này quảng cáo các địa chỉ IP chung và hoạt động theo nhóm để nếu có sự cố trên một trong các máy chủ, yêu cầu của người dùng sẽ không bị mất. Đối với HTTPS và WSS, chính những máy chủ này sẽ mã hóa lưu lượng truy cập để tự mình gánh một phần tải CPU.

Chúng tôi sẽ không nói thêm về WSS và RTMP mà chỉ nói về các yêu cầu HTTPS tiêu chuẩn, thường được liên kết với một dự án web.

Backend

Phía sau mặt trước thường có các máy chủ phụ trợ. Họ xử lý các yêu cầu mà máy chủ phía trước nhận được từ khách hàng.

máy chủ kPHP, trên đó daemon HTTP đang chạy, vì HTTPS đã được giải mã. kPHP là một máy chủ chạy trên mô hình prefork: bắt đầu một quy trình chính, một loạt các quy trình con, chuyển các ổ cắm nghe cho chúng và chúng xử lý các yêu cầu của chúng. Trong trường hợp này, các quy trình không được khởi động lại giữa mỗi yêu cầu từ người dùng mà chỉ cần đặt lại trạng thái của chúng về trạng thái giá trị XNUMX ban đầu - hết yêu cầu này đến yêu cầu khác, thay vì khởi động lại.

Phân phối tải

Tất cả các chương trình phụ trợ của chúng tôi không phải là một nhóm máy khổng lồ có thể xử lý bất kỳ yêu cầu nào. Chúng tôi, họ chia thành các nhóm riêng biệt: chung, di động, api, video, dàn dựng... Sự cố trên một nhóm máy riêng biệt sẽ không ảnh hưởng đến tất cả các máy khác. Trong trường hợp video gặp sự cố, người dùng nghe nhạc thậm chí sẽ không biết về sự cố. Phần phụ trợ nào sẽ gửi yêu cầu được quyết định bởi nginx ở mặt trước theo cấu hình.

Thu thập và cân bằng lại số liệu

Để biết mỗi nhóm cần có bao nhiêu ô tô, chúng ta đừng dựa vào QPS. Các chương trình phụ trợ khác nhau, chúng có các yêu cầu khác nhau, mỗi yêu cầu có độ phức tạp tính toán QPS khác nhau. Đó là lý do tại sao chúng tôi chúng tôi hoạt động với khái niệm tải trên toàn bộ máy chủ - trên CPU và hiệu suất.

Chúng tôi có hàng ngàn máy chủ như vậy. Mỗi máy chủ vật lý chạy một nhóm kPHP để tái chế tất cả các lõi (vì kPHP là một luồng).

Máy chủ nội dung

CS hay Content Server là nơi lưu trữ. CS là một máy chủ lưu trữ các tệp và cũng xử lý các tệp đã tải lên cũng như tất cả các loại tác vụ đồng bộ nền mà giao diện người dùng web chính gán cho nó.

Chúng tôi có hàng chục nghìn máy chủ vật lý lưu trữ tệp. Người dùng thích tải tệp lên và chúng tôi thích lưu trữ và chia sẻ chúng. Một số máy chủ này bị đóng bởi các máy chủ pu/pp đặc biệt.

pu/pp

Nếu bạn mở tab mạng trong VK, bạn sẽ thấy pu/pp.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

pu/pp là gì? Nếu chúng ta đóng hết máy chủ này đến máy chủ khác thì có hai tùy chọn để tải lên và tải tệp xuống máy chủ đã bị đóng: trực tiếp xuyên qua http://cs100500.userapi.com/path hoặc thông qua máy chủ trung gian - http://pu.vk.com/c100500/path.

Pu là tên lịch sử của việc tải ảnh lên và pp là proxy ảnh. Nghĩa là, một máy chủ dùng để tải ảnh lên và một máy chủ khác dùng để tải lên. Bây giờ không chỉ ảnh được tải mà tên cũng được giữ nguyên.

Những máy chủ này chấm dứt phiên HTTPSđể loại bỏ tải bộ xử lý khỏi bộ lưu trữ. Ngoài ra, vì các tệp người dùng được xử lý trên các máy chủ này nên thông tin được lưu trữ trên các máy này càng ít nhạy cảm thì càng tốt. Ví dụ: khóa mã hóa HTTPS.

Vì các máy này bị đóng bởi các máy khác của chúng tôi nên chúng tôi có thể không cấp cho chúng các IP bên ngoài “màu trắng” và cho "màu xám". Bằng cách này, chúng tôi đã lưu trên nhóm IP và đảm bảo bảo vệ máy khỏi sự truy cập từ bên ngoài - đơn giản là không có IP nào có thể xâm nhập vào đó.

Khả năng phục hồi trên các IP được chia sẻ. Về khả năng chịu lỗi, sơ đồ hoạt động giống nhau - một số máy chủ vật lý có IP vật lý chung và phần cứng phía trước chúng chọn nơi gửi yêu cầu. Tôi sẽ nói về các lựa chọn khác sau.

Điểm gây tranh cãi là trong trường hợp này khách hàng giữ ít kết nối hơn. Nếu có cùng một IP cho nhiều máy - với cùng một máy chủ: pu.vk.com hoặc pp.vk.com, trình duyệt máy khách có giới hạn về số lượng yêu cầu đồng thời tới một máy chủ. Nhưng trong thời đại HTTP/2 phổ biến, tôi tin rằng điều này không còn phù hợp nữa.

Nhược điểm rõ ràng của kế hoạch này là nó phải bơm tất cả lưu lượng truy cập, đi vào bộ lưu trữ, thông qua một máy chủ khác. Vì chúng tôi bơm lưu lượng truy cập qua máy nên chúng tôi chưa thể bơm lưu lượng truy cập lớn, chẳng hạn như video, bằng cách sử dụng cùng một sơ đồ. Chúng tôi truyền trực tiếp - một kết nối trực tiếp riêng biệt cho các kho lưu trữ riêng biệt dành riêng cho video. Chúng tôi truyền tải nội dung nhẹ hơn thông qua proxy.

Cách đây không lâu, chúng tôi đã có phiên bản proxy cải tiến. Bây giờ tôi sẽ cho bạn biết chúng khác với những cái thông thường như thế nào và tại sao điều này lại cần thiết.

mặt trời

Vào tháng 2017 năm XNUMX, Oracle, công ty trước đó đã mua Sun, sa thải một lượng lớn nhân viên của Sun. Có thể nói rằng tại thời điểm này công ty đã không còn tồn tại. Khi chọn tên cho hệ thống mới, các quản trị viên của chúng tôi đã quyết định tưởng nhớ công ty này và đặt tên cho hệ thống mới là Sun. Giữa chúng tôi, chúng tôi chỉ đơn giản gọi cô ấy là “mặt trời”.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

pp gặp một số vấn đề. Một IP cho mỗi nhóm - bộ đệm không hiệu quả. Một số máy chủ vật lý chia sẻ một địa chỉ IP chung và không có cách nào để kiểm soát yêu cầu sẽ đến máy chủ nào. Do đó, nếu những người dùng khác nhau truy cập cùng một tệp, thì nếu có bộ đệm trên các máy chủ này, thì tệp sẽ nằm trong bộ đệm của mỗi máy chủ. Đây là một kế hoạch rất kém hiệu quả, nhưng không thể làm gì được.

Do đó - chúng tôi không thể chia nhỏ nội dung, bởi vì chúng tôi không thể chọn một máy chủ cụ thể cho nhóm này - họ có một IP chung. Ngoài ra vì một số lý do nội bộ, chúng tôi có không thể cài đặt các máy chủ như vậy ở các khu vực. Họ chỉ đứng ở St. Petersburg.

Với mặt trời, chúng tôi đã thay đổi hệ thống tuyển chọn. Bây giờ chúng tôi có định tuyến Anycast: định tuyến động, Anycast, daemon tự kiểm tra. Mỗi máy chủ có IP riêng nhưng có một mạng con chung. Mọi thứ đều được cấu hình theo cách mà nếu một máy chủ bị lỗi, lưu lượng truy cập sẽ tự động lan truyền sang các máy chủ khác trong cùng nhóm. Bây giờ có thể chọn một máy chủ cụ thể, không có bộ nhớ đệm dư thừavà độ tin cậy không bị ảnh hưởng.

Hỗ trợ cân nặng. Giờ đây, chúng ta có đủ khả năng lắp đặt các máy có công suất khác nhau nếu cần, đồng thời, trong trường hợp có sự cố tạm thời, hãy thay đổi trọng lượng của các “mặt trời” đang hoạt động để giảm tải cho chúng, để chúng “nghỉ ngơi” và bắt đầu hoạt động trở lại.

Phân chia theo id nội dung. Một điều buồn cười về shending: chúng tôi thường phân chia nội dung để những người dùng khác nhau truy cập cùng một tệp thông qua cùng một “mặt trời” để họ có một bộ đệm chung.

Gần đây chúng tôi đã ra mắt ứng dụng “Clover”. Đây là một bài kiểm tra trực tuyến được phát sóng trực tiếp, trong đó người dẫn chương trình đặt câu hỏi và người dùng trả lời theo thời gian thực, chọn các phương án. Ứng dụng này có một cuộc trò chuyện nơi người dùng có thể trò chuyện. Có thể đồng thời kết nối với chương trình phát sóng hơn 100 nghìn người. Tất cả họ đều viết tin nhắn gửi cho tất cả người tham gia và hình đại diện đi kèm với tin nhắn. Nếu 100 nghìn người đến để tìm một hình đại diện trong một “mặt trời” thì đôi khi nó có thể trôi theo mây.

Để chống lại các đợt yêu cầu đối với cùng một tệp, đối với một loại nội dung nhất định, chúng tôi bật một sơ đồ ngu ngốc nhằm phát tán các tệp trên tất cả các “mặt trời” có sẵn trong khu vực.

Mặt trời từ bên trong

Proxy ngược trên nginx, bộ đệm trong RAM hoặc trên đĩa Optane/NVMe nhanh. Ví dụ: http://sun4-2.userapi.com/c100500/path — một liên kết đến “mặt trời”, nằm ở khu vực thứ tư, nhóm máy chủ thứ hai. Nó đóng tệp đường dẫn nằm trên máy chủ 100500.

Bộ nhớ cache

Chúng tôi thêm một nút nữa vào sơ đồ kiến ​​trúc của mình - môi trường bộ nhớ đệm.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Dưới đây là sơ đồ bố trí bộ nhớ đệm khu vực, có khoảng 20 người trong số họ. Đây là những nơi đặt bộ nhớ đệm và “mặt trời”, có thể tự lưu vào bộ nhớ đệm lưu lượng truy cập.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Đây là bộ nhớ đệm của nội dung đa phương tiện; không có dữ liệu người dùng nào được lưu trữ ở đây - chỉ có nhạc, video, ảnh.

Để xác định khu vực của người dùng, chúng tôi chúng tôi thu thập các tiền tố mạng BGP được công bố trong các khu vực. Trong trường hợp dự phòng, chúng tôi cũng phải phân tích cơ sở dữ liệu địa lý nếu chúng tôi không thể tìm thấy IP theo tiền tố. Chúng tôi xác định khu vực theo IP của người dùng. Trong mã, chúng ta có thể xem xét một hoặc nhiều khu vực của người dùng - những điểm mà anh ta ở gần nhất về mặt địa lý.

Nó hoạt động như thế nào?

Chúng tôi tính mức độ phổ biến của các tệp theo khu vực. Có một số bộ đệm khu vực nơi người dùng được định vị và số nhận dạng tệp - chúng tôi lấy cặp này và tăng xếp hạng sau mỗi lần tải xuống.

Đồng thời, lũ quỷ - các dịch vụ trong khu vực - thỉnh thoảng tìm đến API và nói: “Tôi là một bộ đệm như vậy, hãy cung cấp cho tôi danh sách các tệp phổ biến nhất trong khu vực mà tôi chưa có. ” API cung cấp một loạt tệp được sắp xếp theo xếp hạng, trình nền sẽ tải chúng xuống, đưa chúng đến các khu vực và phân phối tệp từ đó. Đây là điểm khác biệt cơ bản giữa pu/pp và Sun từ bộ đệm: chúng tự cung cấp tệp ngay lập tức, ngay cả khi tệp này không có trong bộ đệm và bộ đệm trước tiên sẽ tải tệp xuống chính nó rồi bắt đầu gửi lại.

Trong trường hợp này chúng tôi nhận được nội dung gần gũi hơn với người dùng và phân tán tải mạng. Ví dụ: chỉ từ bộ đệm Moscow, chúng tôi mới phân phối hơn 1 Tbit/s trong giờ cao điểm.

Nhưng có vấn đề - máy chủ bộ đệm không phải là cao su. Đối với những nội dung siêu phổ biến, đôi khi không đủ mạng cho một máy chủ riêng. Máy chủ bộ đệm của chúng tôi có tốc độ 40-50 Gbit/s, nhưng có nội dung làm tắc nghẽn hoàn toàn kênh như vậy. Chúng tôi đang tiến tới triển khai việc lưu trữ nhiều bản sao của các tệp phổ biến trong khu vực. Tôi hy vọng rằng chúng tôi sẽ thực hiện nó vào cuối năm nay.

Chúng tôi nhìn vào kiến ​​trúc tổng thể.

  • Máy chủ phía trước chấp nhận yêu cầu.
  • Phần phụ trợ xử lý các yêu cầu.
  • Kho được đóng bởi hai loại proxy.
  • Bộ nhớ đệm khu vực.

Sơ đồ này còn thiếu gì? Tất nhiên, cơ sở dữ liệu nơi chúng tôi lưu trữ dữ liệu.

Cơ sở dữ liệu hoặc công cụ

Chúng tôi gọi chúng không phải là cơ sở dữ liệu mà là công cụ - Công cụ, bởi vì thực tế chúng tôi không có cơ sở dữ liệu theo nghĩa được chấp nhận rộng rãi.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Đây là một biện pháp cần thiết.. Điều này xảy ra bởi vì vào năm 2008-2009, khi VK có sự phát triển bùng nổ về mức độ phổ biến, dự án hoạt động hoàn toàn trên MySQL và Memcache và đã xảy ra vấn đề. MySQL thường gặp sự cố và làm hỏng các tệp, sau đó nó sẽ không thể phục hồi và Memcache dần dần giảm hiệu suất và phải được khởi động lại.

Hóa ra dự án ngày càng phổ biến này có bộ lưu trữ liên tục, làm hỏng dữ liệu và bộ đệm, làm chậm. Trong điều kiện như vậy, rất khó để phát triển một dự án đang phát triển. Chúng tôi quyết định cố gắng viết lại những điều quan trọng mà dự án tập trung vào những chiếc xe đạp của chúng tôi.

Giải pháp đã thành công. Có một cơ hội để làm điều này và cũng là một điều cực kỳ cần thiết, bởi vì các cách mở rộng quy mô khác không tồn tại vào thời điểm đó. Không có nhiều cơ sở dữ liệu, NoSQL chưa tồn tại, chỉ có MySQL, Memcache, PostrgreSQL - và chỉ thế thôi.

Hoạt động phổ quát. Quá trình phát triển được dẫn dắt bởi nhóm nhà phát triển C của chúng tôi và mọi thứ đều được thực hiện một cách nhất quán. Bất kể công cụ nào, chúng đều có định dạng tệp gần giống nhau được ghi vào đĩa, cùng các tham số khởi chạy, xử lý tín hiệu theo cùng một cách và hoạt động gần giống nhau trong trường hợp có các tình huống và sự cố biên. Với sự phát triển của công cụ, việc quản trị viên vận hành hệ thống trở nên thuận tiện - không cần bảo trì vườn thú và họ phải học lại cách vận hành từng cơ sở dữ liệu mới của bên thứ ba, điều này giúp tăng nhanh chóng và thuận tiện số của họ.

Các loại động cơ

Nhóm đã viết khá nhiều động cơ. Đây chỉ là một số trong số đó: bạn bè, gợi ý, hình ảnh, ipdb, thư, danh sách, nhật ký, memcached, meowdb, tin tức, nostradamus, ảnh, danh sách phát, pmemcached, sandbox, tìm kiếm, lưu trữ, lượt thích, nhiệm vụ, …

Đối với mỗi tác vụ yêu cầu cấu trúc dữ liệu cụ thể hoặc xử lý các yêu cầu không điển hình, nhóm C sẽ viết một công cụ mới. Tại sao không.

Chúng tôi có động cơ riêng memcached, tương tự như một cái thông thường, nhưng có rất nhiều tính năng thú vị và không bị chậm lại. Không phải ClickHouse, nhưng nó cũng hoạt động. Có sẵn riêng pmemcached - bộ nhớ đệm liên tục, ngoài ra còn có thể lưu trữ dữ liệu trên đĩa, vừa với RAM nên không bị mất dữ liệu khi khởi động lại. Có nhiều công cụ khác nhau cho các tác vụ riêng lẻ: hàng đợi, danh sách, bộ - mọi thứ mà dự án của chúng tôi yêu cầu.

Cụm

Từ góc độ mã, không cần phải coi công cụ hoặc cơ sở dữ liệu là các quy trình, thực thể hoặc phiên bản. Mã hoạt động cụ thể với các cụm, với các nhóm động cơ - một loại cho mỗi cụm. Giả sử có một cụm memcached - đó chỉ là một nhóm máy.

Mã không cần biết vị trí thực tế, kích thước hoặc số lượng máy chủ. Anh ta đi đến cụm bằng cách sử dụng một mã định danh nhất định.

Để tính năng này hoạt động, bạn cần thêm một thực thể nữa nằm giữa mã và công cụ - Proxy.

Proxy RPC

Ủy quyền xe buýt kết nối, trên đó gần như toàn bộ trang web chạy. Đồng thời chúng tôi có không có khám phá dịch vụ — thay vào đó, có một cấu hình cho proxy này, proxy này biết vị trí của tất cả các cụm và tất cả các phân đoạn của cụm này. Đây là những gì quản trị viên làm.

Các lập trình viên không quan tâm đến chi phí bao nhiêu, ở đâu và bao nhiêu - họ chỉ đi đến cụm. Điều này cho phép chúng tôi rất nhiều. Khi nhận được yêu cầu, proxy sẽ chuyển hướng yêu cầu, biết vị trí - nó tự xác định điều này.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Trong trường hợp này, proxy là một điểm bảo vệ khỏi lỗi dịch vụ. Nếu một số công cụ chạy chậm hoặc gặp sự cố thì proxy sẽ hiểu điều này và phản hồi tương ứng cho phía máy khách. Điều này cho phép bạn loại bỏ thời gian chờ - mã không đợi động cơ phản hồi nhưng hiểu rằng nó không hoạt động và cần phải hành xử khác đi. Mã phải được chuẩn bị cho thực tế là cơ sở dữ liệu không phải lúc nào cũng hoạt động.

Triển khai cụ thể

Đôi khi chúng tôi vẫn thực sự muốn có một giải pháp phi tiêu chuẩn nào đó làm động cơ. Đồng thời, người ta quyết định không sử dụng proxy rpc làm sẵn của chúng tôi, được tạo riêng cho công cụ của chúng tôi, mà tạo một proxy riêng cho nhiệm vụ.

Đối với MySQL, chúng tôi vẫn có ở đây và ở đó, chúng tôi sử dụng db-proxy và đối với ClickHouse - Nhà mèo con.

Nó hoạt động nói chung như thế này. Có một máy chủ nhất định, nó chạy kPHP, Go, Python - nói chung là bất kỳ mã nào có thể sử dụng giao thức RPC của chúng tôi. Mã chạy cục bộ trên proxy RPC - mỗi máy chủ chứa mã sẽ chạy proxy cục bộ của riêng nó. Theo yêu cầu, proxy sẽ hiểu nơi cần đi.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Nếu một công cụ muốn chuyển sang công cụ khác, ngay cả khi đó là công cụ hàng xóm, nó sẽ thông qua proxy, vì công cụ hàng xóm có thể ở một trung tâm dữ liệu khác. Động cơ không nên dựa vào việc biết vị trí của bất kỳ thứ gì khác ngoài chính nó - đây là giải pháp tiêu chuẩn của chúng tôi. Nhưng tất nhiên vẫn có ngoại lệ :)

Một ví dụ về sơ đồ TL mà tất cả các động cơ đều hoạt động.

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

Đây là một giao thức nhị phân, tương tự gần nhất là protobuf. Lược đồ mô tả trước các trường tùy chọn, các loại phức tạp - phần mở rộng của vô hướng tích hợp và truy vấn. Mọi thứ hoạt động theo giao thức này.

RPC qua TL qua TCP/UDP… UDP?

Chúng tôi có giao thức RPC để thực hiện các yêu cầu công cụ chạy trên sơ đồ TL. Tất cả điều này hoạt động trên kết nối TCP/UDP. TCP là điều dễ hiểu, nhưng tại sao chúng ta lại thường xuyên cần đến UDP?

UDP giúp tránh vấn đề số lượng lớn kết nối giữa các máy chủ. Nếu mỗi máy chủ có một proxy RPC và nói chung, nó có thể truy cập vào bất kỳ công cụ nào thì sẽ có hàng chục nghìn kết nối TCP trên mỗi máy chủ. Có tải nhưng vô ích. Trong trường hợp UDP, vấn đề này không tồn tại.

Không có bắt tay TCP dư thừa. Đây là một vấn đề điển hình: khi một công cụ mới hoặc một máy chủ mới được khởi chạy, nhiều kết nối TCP được thiết lập cùng một lúc. Đối với các yêu cầu nhỏ nhẹ, chẳng hạn như tải trọng UDP, tất cả giao tiếp giữa mã và công cụ đều được thực hiện hai gói UDP: một con bay về một hướng, con thứ hai bay về hướng khác. Một chuyến đi khứ hồi - và mã nhận được phản hồi từ động cơ mà không cần bắt tay.

Vâng, tất cả chỉ hoạt động với tỷ lệ mất gói rất nhỏ. Giao thức có hỗ trợ truyền lại và hết thời gian chờ, nhưng nếu mất nhiều, chúng ta sẽ nhận được gần như TCP, điều này không mang lại lợi nhuận. Chúng tôi không thúc đẩy UDP xuyên đại dương.

Chúng tôi có hàng nghìn máy chủ như vậy và sơ đồ giống nhau: một gói công cụ được cài đặt trên mỗi máy chủ vật lý. Chúng chủ yếu là các luồng đơn để chạy nhanh nhất có thể mà không bị chặn và được phân chia thành các giải pháp đơn luồng. Đồng thời, chúng tôi không có gì đáng tin cậy hơn những công cụ này và rất nhiều sự chú ý đến việc lưu trữ dữ liệu liên tục.

Lưu trữ dữ liệu liên tục

Động cơ viết binlog. Binlog là một tệp ở cuối tệp có thêm sự kiện thay đổi trạng thái hoặc dữ liệu. Trong các giải pháp khác nhau, nó được gọi khác nhau: nhật ký nhị phân, WAL, AOF, nhưng nguyên tắc là như nhau.

Để ngăn động cơ đọc lại toàn bộ binlog trong nhiều năm khi khởi động lại, động cơ ghi ảnh chụp nhanh - trạng thái hiện tại. Nếu cần, họ đọc từ đó trước, sau đó đọc xong từ binlog. Tất cả các binlog đều được viết ở cùng một định dạng nhị phân - theo sơ đồ TL, để quản trị viên có thể quản lý chúng một cách bình đẳng bằng các công cụ của họ. Không có nhu cầu chụp ảnh nhanh như vậy. Có một tiêu đề chung cho biết ảnh chụp nhanh của ai là int, ma thuật của động cơ và nội dung nào không quan trọng đối với bất kỳ ai. Đây là sự cố với công cụ ghi lại ảnh chụp nhanh.

Tôi sẽ mô tả nhanh nguyên lý hoạt động. Có một máy chủ mà động cơ chạy trên đó. Anh ta mở một binlog trống mới để viết và viết một sự kiện để thay đổi nó.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Tại một thời điểm nào đó, anh ấy quyết định tự mình chụp ảnh nhanh hoặc nhận được tín hiệu. Máy chủ tạo một tệp mới, ghi toàn bộ trạng thái của nó vào đó, gắn kích thước binlog hiện tại - offset - vào cuối tệp và tiếp tục ghi thêm. Một binlog mới không được tạo.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Tại một thời điểm nào đó, khi động cơ khởi động lại, sẽ có cả binlog và ảnh chụp nhanh trên đĩa. Công cụ đọc toàn bộ ảnh chụp nhanh và tăng trạng thái của nó tại một điểm nhất định.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Đọc vị trí tại thời điểm ảnh chụp nhanh được tạo và kích thước của binlog.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Đọc phần cuối của binlog để biết trạng thái hiện tại và tiếp tục viết các sự kiện tiếp theo. Đây là một sơ đồ đơn giản; tất cả các động cơ của chúng tôi đều hoạt động theo nó.

Sao chép dữ liệu

Kết quả là, việc sao chép dữ liệu trong dựa trên tuyên bố — chúng tôi viết trong binlog không phải bất kỳ thay đổi trang nào, mà cụ thể là yêu cầu thay đổi. Rất giống với những gì được truyền qua mạng, chỉ được sửa đổi một chút.

Sơ đồ tương tự không chỉ được sử dụng để nhân rộng mà còn để tạo bản sao lưu. Chúng tôi có một công cụ - một bậc thầy viết lách ghi vào binlog. Ở bất kỳ nơi nào khác mà quản trị viên thiết lập, binlog này sẽ được sao chép và thế là xong - chúng tôi có một bản sao lưu.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Nếu cần thiết đọc bản saoĐể giảm tải việc đọc của CPU, công cụ đọc chỉ cần khởi chạy, công cụ này sẽ đọc phần cuối của binlog và thực thi các lệnh này cục bộ.

Độ trễ ở đây rất nhỏ và có thể tìm ra bản sao tụt hậu so với bản gốc bao nhiêu.

Phân chia dữ liệu trong proxy RPC

Phân mảnh hoạt động như thế nào? Làm cách nào để proxy hiểu phân đoạn cụm nào sẽ được gửi đến? Mã không có nội dung: "Gửi 15 mảnh!" - không, việc này được thực hiện bởi người ủy quyền.

Sơ đồ đơn giản nhất là firstint — số đầu tiên trong yêu cầu.

get(photo100_500) => 100 % N.

Đây là ví dụ về giao thức văn bản được ghi nhớ đơn giản, nhưng tất nhiên, các truy vấn có thể phức tạp và có cấu trúc. Ví dụ lấy số đầu tiên trong truy vấn và số còn lại khi chia cho kích thước cụm.

Điều này hữu ích khi chúng ta muốn có vị trí dữ liệu của một thực thể. Giả sử 100 là ID người dùng hoặc nhóm và chúng tôi muốn tất cả dữ liệu của một thực thể nằm trên một phân đoạn cho các truy vấn phức tạp.

Nếu chúng tôi không quan tâm đến việc các yêu cầu được phân bổ như thế nào trên cụm thì có một tùy chọn khác - băm toàn bộ phân đoạn.

hash(photo100_500) => 3539886280 % N

Chúng tôi cũng nhận được hàm băm, phần còn lại của phép chia và số phân đoạn.

Cả hai tùy chọn này chỉ hoạt động nếu chúng ta chuẩn bị cho thực tế là khi tăng kích thước của cụm, chúng ta sẽ chia nhỏ hoặc tăng nó lên gấp nhiều lần. Ví dụ: chúng tôi có 16 phân đoạn, chúng tôi không có đủ, chúng tôi muốn nhiều hơn - chúng tôi có thể nhận được 32 ​​một cách an toàn mà không có thời gian chết. Nếu chúng ta muốn tăng chứ không phải bội số thì sẽ có thời gian ngừng hoạt động vì chúng ta sẽ không thể chia nhỏ mọi thứ một cách chính xác mà không bị lỗ. Các tùy chọn này rất hữu ích, nhưng không phải lúc nào cũng vậy.

Nếu chúng tôi cần thêm hoặc xóa số lượng máy chủ tùy ý, chúng tôi sử dụng Băm liên tục trên võ đài a la Ketama. Nhưng đồng thời, chúng tôi mất hoàn toàn vị trí của dữ liệu; chúng tôi phải hợp nhất yêu cầu vào cụm để mỗi phần trả về phản hồi nhỏ của riêng nó, sau đó hợp nhất các phản hồi với proxy.

Có những yêu cầu siêu cụ thể. Nó trông như thế này: Proxy RPC nhận được yêu cầu, xác định cụm nào sẽ đi tới và xác định phân đoạn. Sau đó, có các bậc thầy viết hoặc nếu cụm có hỗ trợ bản sao, nó sẽ gửi tới một bản sao theo yêu cầu. Proxy thực hiện tất cả điều này.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Nhật ký

Chúng tôi viết nhật ký theo nhiều cách. Điều rõ ràng và đơn giản nhất là ghi nhật ký vào memcache.

ring-buffer: prefix.idx = line

Có một tiền tố chính - tên của nhật ký, một dòng và có kích thước của nhật ký này - số lượng dòng. Chúng ta lấy một số ngẫu nhiên từ 0 đến số dòng trừ đi 1. Key trong memcache là tiền tố được nối với số ngẫu nhiên này. Chúng tôi lưu dòng nhật ký và thời gian hiện tại vào giá trị.

Khi cần đọc nhật ký, chúng tôi thực hiện Nhận nhiều tất cả các khóa, được sắp xếp theo thời gian và do đó có được nhật ký sản xuất theo thời gian thực. Lược đồ này được sử dụng khi bạn cần gỡ lỗi thứ gì đó trong quá trình sản xuất theo thời gian thực mà không vi phạm bất kỳ thứ gì, không dừng hoặc cho phép lưu lượng truy cập đến các máy khác, nhưng nhật ký này không tồn tại lâu.

Để lưu trữ nhật ký đáng tin cậy, chúng tôi có một công cụ công cụ ghi nhật ký. Đây chính xác là lý do tại sao nó được tạo ra và được sử dụng rộng rãi trong một số lượng lớn các cụm. Cụm lớn nhất mà tôi biết lưu trữ 600 TB nhật ký được đóng gói.

Máy cũ lắm rồi, có cụm cũng 6-7 năm rồi. Có những vấn đề với nó mà chúng tôi đang cố gắng giải quyết, chẳng hạn như chúng tôi bắt đầu tích cực sử dụng ClickHouse để lưu trữ nhật ký.

Thu thập nhật ký trong ClickHouse

Sơ đồ này cho thấy cách chúng tôi bước vào động cơ của mình.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Có mã chuyển cục bộ qua RPC tới RPC-proxy và nó hiểu nơi cần chuyển đến công cụ. Nếu muốn viết nhật ký trong ClickHouse, chúng ta cần thay đổi hai phần trong sơ đồ này:

  • thay thế một số công cụ bằng ClickHouse;
  • thay thế proxy RPC không thể truy cập ClickHouse bằng một số giải pháp có thể và thông qua RPC.

Công cụ rất đơn giản - chúng tôi thay thế nó bằng một máy chủ hoặc một cụm máy chủ bằng ClickHouse.

Và để truy cập ClickHouse, chúng tôi đã làm Mèo ConNhà. Nếu chúng ta chuyển trực tiếp từ KittenHouse sang ClickHouse, điều đó sẽ không thành công. Ngay cả khi không có yêu cầu, nó vẫn tăng lên từ các kết nối HTTP của một số lượng lớn máy. Để chương trình hoạt động, trên máy chủ có ClickHouse proxy ngược cục bộ được nâng lên, được viết theo cách có thể chịu được khối lượng kết nối cần thiết. Nó cũng có thể đệm dữ liệu trong chính nó một cách tương đối đáng tin cậy.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Đôi khi chúng tôi không muốn triển khai lược đồ RPC trong các giải pháp không chuẩn, chẳng hạn như trong nginx. Vì vậy, KittenHouse có khả năng nhận log qua UDP.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Nếu người gửi và người nhận nhật ký hoạt động trên cùng một máy thì khả năng mất gói UDP trong máy chủ cục bộ là khá thấp. Để cân bằng giữa nhu cầu triển khai RPC trong giải pháp của bên thứ ba và độ tin cậy, chúng tôi chỉ cần sử dụng tính năng gửi UDP. Chúng ta sẽ quay lại sơ đồ này sau.

Giám sát

Chúng tôi có hai loại nhật ký: nhật ký do quản trị viên thu thập trên máy chủ của họ và nhật ký do nhà phát triển viết từ mã. Chúng tương ứng với hai loại số liệu: hệ thống và sản phẩm.

Số liệu hệ thống

Nó hoạt động trên tất cả các máy chủ của chúng tôi netdata, thu thập số liệu thống kê và gửi chúng đến than chì cacbon. Do đó, ClickHouse được sử dụng làm hệ thống lưu trữ chứ không phải Whisper chẳng hạn. Nếu cần, bạn có thể đọc trực tiếp từ ClickHouse hoặc sử dụng grafana cho các số liệu, đồ thị và báo cáo. Với tư cách là nhà phát triển, chúng tôi có đủ quyền truy cập vào Netdata và Grafana.

Số liệu sản phẩm

Để thuận tiện, chúng tôi đã viết rất nhiều thứ. Ví dụ: có một tập hợp các hàm thông thường cho phép bạn ghi các giá trị Counts, UniqueCounts vào số liệu thống kê, chúng sẽ được gửi đi đâu đó xa hơn.

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

Sau đó, chúng tôi có thể sử dụng các bộ lọc sắp xếp và nhóm và thực hiện mọi thứ chúng tôi muốn từ thống kê - xây dựng biểu đồ, định cấu hình Cơ quan giám sát.

Chúng tôi viết rất nhiều số liệu số lượng sự kiện là từ 600 tỷ đến 1 nghìn tỷ mỗi ngày. Tuy nhiên, chúng tôi muốn giữ chúng ít nhất một vài nămđể hiểu xu hướng trong số liệu. Đặt tất cả lại với nhau là một vấn đề lớn mà chúng ta vẫn chưa giải quyết được. Tôi sẽ cho bạn biết nó đã hoạt động như thế nào trong vài năm qua.

Chúng tôi có các chức năng viết các số liệu này tới memcache địa phươngđể giảm số lượng mục. Một lần trong một khoảng thời gian ngắn được ra mắt tại địa phương số liệu thống kê-daemon thu thập tất cả các hồ sơ. Tiếp theo, con quỷ hợp nhất các số liệu thành hai lớp máy chủ người thu thập nhật ký, tổng hợp số liệu thống kê từ một loạt máy của chúng tôi để lớp đằng sau chúng không bị chết.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Nếu cần, chúng tôi có thể viết thư trực tiếp cho người thu thập nhật ký.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Tuy nhiên, việc viết từ mã trực tiếp đến bộ thu thập, bỏ qua stas-daemom, là một giải pháp có khả năng mở rộng kém vì nó làm tăng tải cho bộ thu thập. Giải pháp chỉ phù hợp nếu vì lý do nào đó mà chúng tôi không thể nâng cấp daemon thống kê memcache trên máy hoặc nó bị lỗi và chúng tôi đã truy cập trực tiếp.

Tiếp theo, trình thu thập nhật ký hợp nhất số liệu thống kê vào meoDB - đây là cơ sở dữ liệu của chúng tôi, cũng có thể lưu trữ số liệu.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Sau đó, chúng ta có thể thực hiện các lựa chọn nhị phân “gần SQL” từ mã.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Thí nghiệm

Vào mùa hè năm 2018, chúng tôi đã tổ chức một cuộc thi hackathon nội bộ và nảy sinh ý tưởng thay thế phần màu đỏ của sơ đồ bằng thứ gì đó có thể lưu trữ số liệu trong ClickHouse. Chúng tôi có nhật ký trên ClickHouse - tại sao không thử?

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Chúng tôi đã có một kế hoạch viết nhật ký thông qua KittenHouse.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Chúng tôi đã quyết định thêm một “*House” khác vào sơ đồ, sẽ nhận chính xác các số liệu ở định dạng khi mã của chúng tôi viết chúng thông qua UDP. Sau đó, *House này biến chúng thành các phần chèn, giống như các khúc gỗ, mà KittenHouse hiểu được. Anh ấy hoàn toàn có thể gửi những nhật ký này đến ClickHouse để ClickHouse có thể đọc được chúng.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Lược đồ với cơ sở dữ liệu memcache, stats-daemon và log-collectors được thay thế bằng lược đồ này.

Hỏi đáp về kiến ​​trúc và công việc của VKontakte

Lược đồ với cơ sở dữ liệu memcache, stats-daemon và log-collectors được thay thế bằng lược đồ này.

  • Có một công văn từ mã ở đây, được viết cục bộ trong StatsHouse.
  • StatsHouse ghi các số liệu UDP, đã được chuyển đổi thành các phần chèn SQL, vào KittenHouse theo đợt.
  • KittenHouse gửi chúng đến ClickHouse.
  • Nếu chúng tôi muốn đọc chúng, chúng tôi sẽ đọc chúng bằng cách bỏ qua StatsHouse - trực tiếp từ ClickHouse bằng SQL thông thường.

Nó vẫn còn chứ một thí nghiệm, nhưng chúng tôi thích cách nó diễn ra. Nếu chúng tôi khắc phục được các vấn đề với chương trình, thì có lẽ chúng tôi sẽ chuyển sang nó hoàn toàn. Cá nhân tôi hy vọng như vậy.

Đề án không tiết kiệm sắt. Cần ít máy chủ hơn, không cần trình nền thống kê cục bộ và trình thu thập nhật ký, nhưng ClickHouse yêu cầu máy chủ lớn hơn các máy chủ trong sơ đồ hiện tại. Cần ít máy chủ hơn nhưng chúng phải đắt hơn và mạnh hơn.

Triển khai

Đầu tiên, hãy xem việc triển khai PHP. Chúng tôi đang phát triển ở git: sử dụng GitLab и TeamCity để triển khai. Các nhánh phát triển được sáp nhập vào nhánh chính, từ nhánh chính để thử nghiệm, chúng được hợp nhất thành nhánh dàn dựng và từ nhánh dàn dựng thành nhánh sản xuất.

Trước khi triển khai, nhánh sản xuất hiện tại và nhánh sản xuất trước đó sẽ được lấy và các tệp khác biệt được xem xét trong đó - các thay đổi: được tạo, xóa, thay đổi. Thay đổi này được ghi lại trong binlog của một công cụ copyfast đặc biệt, công cụ này có thể nhanh chóng sao chép các thay đổi cho toàn bộ nhóm máy chủ của chúng tôi. Những gì được sử dụng ở đây không phải là sao chép trực tiếp mà là sao chép tin đồn, khi một máy chủ gửi các thay đổi đến những máy chủ lân cận gần nhất của nó, những thay đổi đó tới những máy chủ lân cận của chúng, v.v. Điều này cho phép bạn cập nhật mã trong hàng chục đơn vị giây trên toàn bộ nhóm. Khi thay đổi đến bản sao cục bộ, nó sẽ áp dụng các bản vá này cho bản sao cục bộ của nó. hệ thống tập tin cục bộ. Việc khôi phục cũng được thực hiện theo sơ đồ tương tự.

Chúng tôi cũng triển khai kPHP rất nhiều và nó cũng có sự phát triển riêng trên git theo sơ đồ trên. Vì điều này Máy chủ HTTP nhị phân, thì chúng tôi không thể tạo ra khác biệt - tệp nhị phân phát hành nặng hàng trăm MB. Vì vậy, có một lựa chọn khác ở đây - phiên bản được ghi vào binlog sao chép nhanh. Với mỗi lần xây dựng, nó sẽ tăng lên và trong quá trình khôi phục, nó cũng tăng lên. Phiên bản được sao chép sang máy chủ. Các copyfast địa phương thấy rằng một phiên bản mới đã được đưa vào binlog và bằng cách sao chép tin đồn tương tự, họ lấy phiên bản nhị phân mới nhất cho mình mà không làm mệt máy chủ chính của chúng tôi mà cẩn thận phân bổ tải trên mạng. Những gì tiếp theo khởi động lại duyên dáng cho phiên bản mới.

Đối với các công cụ của chúng tôi, về cơ bản cũng là các hệ nhị phân, sơ đồ này rất giống nhau:

  • nhánh git master;
  • nhị phân trong . Deb;
  • phiên bản được ghi vào binlog copyfast;
  • được sao chép sang máy chủ;
  • máy chủ lấy ra một .dep mới;
  • DPKG-Tôi;
  • duyên dáng khởi chạy lại phiên bản mới.

Sự khác biệt là tệp nhị phân của chúng tôi được đóng gói trong kho lưu trữ . Deb, và khi bơm ra chúng DPKG-Tôi được đặt trên hệ thống. Tại sao kPHP được triển khai dưới dạng nhị phân và các công cụ được triển khai dưới dạng dpkg? Nó đã xảy ra như vậy. Nó hoạt động - đừng chạm vào nó.

Liên kết hữu ích:

Alexey Akulovich là một trong những người, với tư cách là thành viên của Ủy ban Chương trình, đã giúp đỡ PHP Nga vào ngày 17/XNUMX sẽ trở thành sự kiện lớn nhất dành cho các nhà phát triển PHP trong thời gian gần đây. Hãy nhìn xem chúng ta có một chiếc PC tuyệt vời thế nào, diễn giả (hai trong số họ đang phát triển lõi PHP!) - có vẻ như là thứ bạn không thể bỏ lỡ nếu viết PHP.

Nguồn: www.habr.com

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