ประวัติความเป็นมาของการสร้าง VKontakte อยู่ใน Wikipedia โดยพาเวลเล่าเอง ดูเหมือนว่าทุกคนจะรู้จักเธอแล้ว เกี่ยวกับภายใน สถาปัตยกรรม และโครงสร้างของไซต์บน HighLoad++ Pavel
อเล็กเซย์ อาคูโลวิช (
เป็นเวลากว่าสี่ปีแล้วที่ฉันจัดการกับงานทุกประเภทที่เกี่ยวข้องกับแบ็กเอนด์
- การอัปโหลด จัดเก็บ ประมวลผล เผยแพร่สื่อ: วิดีโอ การสตรีมสด เสียง รูปภาพ เอกสาร
- โครงสร้างพื้นฐาน, แพลตฟอร์ม, การตรวจสอบนักพัฒนา, บันทึก, แคชระดับภูมิภาค, CDN, โปรโตคอล RPC ที่เป็นกรรมสิทธิ์
- การบูรณาการกับบริการภายนอก: การแจ้งเตือนแบบพุช, การแยกวิเคราะห์ลิงก์ภายนอก, ฟีด RSS
- ช่วยเหลือเพื่อนร่วมงานด้วยคำถามต่างๆ คำตอบที่ต้องอาศัยโค้ดที่ไม่รู้จัก
ในช่วงเวลานี้ ฉันมีส่วนช่วยในหลายองค์ประกอบของไซต์ ฉันต้องการแบ่งปันประสบการณ์นี้
สถาปัตยกรรมทั่วไป
ตามปกติทุกอย่างจะเริ่มต้นด้วยเซิร์ฟเวอร์หรือกลุ่มเซิร์ฟเวอร์ที่ยอมรับคำขอ
เซิร์ฟเวอร์ด้านหน้า
เซิร์ฟเวอร์ด้านหน้ายอมรับคำขอผ่าน HTTPS, RTMP และ WSS
HTTPS - นี่คือคำขอสำหรับเว็บไซต์เวอร์ชันหลักและเว็บบนมือถือ: vk.com และ m.vk.com และไคลเอนต์ที่เป็นทางการและไม่เป็นทางการอื่น ๆ ของ API ของเรา: ไคลเอนต์มือถือ, ผู้ส่งสาร เรามีแผนกต้อนรับ RTMP- การรับส่งข้อมูลสำหรับการถ่ายทอดสดพร้อมเซิร์ฟเวอร์ด้านหน้าแยกต่างหากและ WSS- การเชื่อมต่อสำหรับ Streaming API
สำหรับ HTTPS และ WSS บนเซิร์ฟเวอร์ถือว่าคุ้มค่า Nginx. สำหรับการออกอากาศ RTMP เราเพิ่งเปลี่ยนมาใช้โซลูชันของเราเอง คิฟแต่อยู่นอกเหนือขอบเขตของรายงาน สำหรับการยอมรับข้อผิดพลาด เซิร์ฟเวอร์เหล่านี้โฆษณาที่อยู่ IP ทั่วไปและดำเนินการเป็นกลุ่ม ดังนั้นหากเกิดปัญหาบนเซิร์ฟเวอร์ใดเซิร์ฟเวอร์หนึ่ง คำขอของผู้ใช้จะไม่สูญหาย สำหรับ HTTPS และ WSS เซิร์ฟเวอร์เดียวกันนี้จะเข้ารหัสการรับส่งข้อมูลเพื่อเป็นส่วนหนึ่งของภาระงานของ CPU
เราจะไม่พูดถึง WSS และ RTMP เพิ่มเติม แต่จะพูดถึงคำขอ HTTPS มาตรฐานเท่านั้น ซึ่งมักจะเกี่ยวข้องกับโครงการเว็บ
แบ็กเอนด์
ด้านหลังด้านหน้ามักจะมีเซิร์ฟเวอร์แบ็กเอนด์ พวกเขาประมวลผลคำขอที่เซิร์ฟเวอร์ด้านหน้าได้รับจากไคลเอนต์
มัน เซิร์ฟเวอร์ kPHPที่ HTTP daemon ทำงานอยู่ เนื่องจาก HTTPS ถูกถอดรหัสแล้ว kPHP เป็นเซิร์ฟเวอร์ที่ทำงานบน โมเดลพรีฟอร์ค: เริ่มกระบวนการหลัก ซึ่งเป็นกระบวนการลูกจำนวนมาก ส่งซ็อกเก็ตการฟังให้พวกเขา และพวกเขาก็ประมวลผลคำขอของพวกเขา ในกรณีนี้ กระบวนการจะไม่เริ่มต้นใหม่ระหว่างแต่ละคำขอจากผู้ใช้ แต่เพียงแค่รีเซ็ตสถานะเป็นสถานะค่าศูนย์ดั้งเดิม - คำขอครั้งแล้วครั้งเล่า แทนที่จะรีสตาร์ท
โหลดการกระจาย
แบ็กเอนด์ทั้งหมดของเราไม่ใช่กลุ่มเครื่องจักรขนาดใหญ่ที่สามารถประมวลผลคำขอใดๆ ได้ เราพวกเขา แบ่งออกเป็นกลุ่มแยกกัน: ทั่วไป, มือถือ, API, วิดีโอ, การแสดงละคร... ปัญหาบนกลุ่มเครื่องที่แยกจากกันจะไม่ส่งผลกระทบต่อเครื่องอื่นๆ ทั้งหมด ในกรณีที่เกิดปัญหากับวิดีโอ ผู้ใช้ที่ฟังเพลงจะไม่ทราบปัญหาด้วยซ้ำ แบ็กเอนด์ใดที่จะส่งคำขอไปนั้นจะถูกตัดสินใจโดย nginx ที่ด้านหน้าตามการกำหนดค่า
การรวบรวมและการปรับสมดุลเมตริก
เพื่อให้เข้าใจว่าในแต่ละกลุ่มเราจะต้องมีรถยนต์กี่คัน ไม่ต้องพึ่ง QPS. แบ็กเอนด์แตกต่างกัน มีคำขอที่แตกต่างกัน แต่ละคำขอมีความซับซ้อนในการคำนวณ QPS ที่แตกต่างกัน นั่นเป็นเหตุผลที่เรา เราดำเนินการโดยใช้แนวคิดเรื่องการโหลดบนเซิร์ฟเวอร์โดยรวม - บน CPU และประสิทธิภาพ.
เรามีเซิร์ฟเวอร์ดังกล่าวหลายพันรายการ ฟิสิคัลเซิร์ฟเวอร์แต่ละตัวจะรันกลุ่ม kPHP เพื่อรีไซเคิลคอร์ทั้งหมด (เนื่องจาก kPHP เป็นแบบเธรดเดียว)
เซิร์ฟเวอร์เนื้อหา
CS หรือ Content Server เป็นที่เก็บข้อมูล. CS คือเซิร์ฟเวอร์ที่จัดเก็บไฟล์และประมวลผลไฟล์ที่อัปโหลดและงานซิงโครนัสในพื้นหลังทุกประเภทที่ส่วนหน้าของเว็บหลักกำหนดไว้
เรามีเซิร์ฟเวอร์ทางกายภาพนับหมื่นเครื่องที่เก็บไฟล์ ผู้ใช้ชอบอัปโหลดไฟล์ และเราชอบที่จะจัดเก็บและแบ่งปันไฟล์เหล่านั้น เซิร์ฟเวอร์เหล่านี้บางส่วนถูกปิดโดยเซิร์ฟเวอร์ pu/pp พิเศษ
ปู/พีพี
หากคุณเปิดแท็บเครือข่ายใน VK คุณจะเห็น pu/pp
pu/pp คืออะไร? หากเราปิดเซิร์ฟเวอร์หนึ่งแล้วเซิร์ฟเวอร์อื่น มีสองตัวเลือกในการอัพโหลดและดาวน์โหลดไฟล์ไปยังเซิร์ฟเวอร์ที่ถูกปิด: โดยตรง ตลอด http://cs100500.userapi.com/path
หรือ ผ่านเซิร์ฟเวอร์ระดับกลาง - http://pu.vk.com/c100500/path
.
Pu เป็นชื่อในอดีตของการอัปโหลดรูปภาพ และ pp คือพร็อกซีรูปภาพ. นั่นคือเซิร์ฟเวอร์หนึ่งใช้สำหรับการอัพโหลดรูปภาพและอีกเซิร์ฟเวอร์หนึ่งใช้สำหรับการอัพโหลด ตอนนี้ไม่เพียงโหลดรูปภาพเท่านั้น แต่ชื่อก็ยังคงอยู่
เซิร์ฟเวอร์เหล่านี้ ยุติเซสชัน HTTPSเพื่อลบโหลดตัวประมวลผลออกจากที่เก็บข้อมูล นอกจากนี้ เนื่องจากไฟล์ผู้ใช้ได้รับการประมวลผลบนเซิร์ฟเวอร์เหล่านี้ ยิ่งข้อมูลที่ละเอียดอ่อนถูกจัดเก็บไว้ในเครื่องเหล่านี้ก็ยิ่งดีเท่านั้น ตัวอย่างเช่น คีย์การเข้ารหัส HTTPS
เนื่องจากเครื่องอื่นๆ ของเราปิดเครื่องไว้ เราจึงไม่สามารถให้ IP ภายนอก "สีขาว" แก่เครื่องเหล่านั้นได้ และ ให้ "สีเทา". ด้วยวิธีนี้เราจึงบันทึกบนพูล IP และรับประกันว่าจะปกป้องเครื่องจากการเข้าถึงจากภายนอก - ไม่มี IP ที่จะเข้าไปได้
ความยืดหยุ่นเหนือ IP ที่ใช้ร่วมกัน. ในแง่ของความทนทานต่อข้อผิดพลาด รูปแบบการทำงานจะเหมือนกัน - เซิร์ฟเวอร์ฟิสิคัลหลายตัวมี IP ฟิสิคัลทั่วไป และฮาร์ดแวร์ที่อยู่ด้านหน้าเซิร์ฟเวอร์จะเลือกตำแหน่งที่จะส่งคำขอ ฉันจะพูดถึงตัวเลือกอื่นในภายหลัง
ประเด็นที่ถกเถียงกันอยู่ก็คือในกรณีนี้ ลูกค้าเก็บการเชื่อมต่อน้อยลง. หากมี IP เดียวกันสำหรับเครื่องหลายเครื่อง - ด้วยโฮสต์เดียวกัน: pu.vk.com หรือ pp.vk.com เบราว์เซอร์ไคลเอนต์จะมีการจำกัดจำนวนคำขอพร้อมกันไปยังโฮสต์เดียว แต่ในช่วงเวลาที่มี HTTP/2 แพร่หลาย ฉันเชื่อว่าสิ่งนี้ไม่เกี่ยวข้องอีกต่อไป
ข้อเสียที่ชัดเจนของโครงการนี้คือต้องทำ สูบสัญจรทั้งหมดซึ่งไปที่พื้นที่เก็บข้อมูลผ่านเซิร์ฟเวอร์อื่น เนื่องจากเราดึงดูดการรับส่งข้อมูลผ่านเครื่องจักร เราจึงยังไม่สามารถดึงดูดการรับส่งข้อมูลจำนวนมาก เช่น วิดีโอ โดยใช้รูปแบบเดียวกันได้ เราส่งสัญญาณโดยตรง - การเชื่อมต่อโดยตรงแยกต่างหากสำหรับพื้นที่เก็บข้อมูลแยกต่างหากสำหรับวิดีโอโดยเฉพาะ เราส่งเนื้อหาที่เบากว่าผ่านพร็อกซี
ไม่นานมานี้ เราได้รับพรอกซีเวอร์ชันปรับปรุงแล้ว ตอนนี้ฉันจะบอกคุณว่าพวกเขาแตกต่างจากคนทั่วไปอย่างไรและเหตุใดจึงจำเป็น
ดวงอาทิตย์
ในเดือนกันยายน 2017 Oracle ซึ่งเคยซื้อ Sun มาก่อน
pp มีปัญหาเล็กน้อย หนึ่ง IP ต่อกลุ่ม - แคชที่ไม่มีประสิทธิภาพ. เซิร์ฟเวอร์จริงหลายเครื่องใช้ที่อยู่ IP ร่วมกัน และไม่มีวิธีควบคุมเซิร์ฟเวอร์ที่คำขอจะส่งไป ดังนั้น หากผู้ใช้ต่างกันเข้ามาเพื่อไฟล์เดียวกัน หากมีแคชบนเซิร์ฟเวอร์เหล่านี้ ไฟล์ก็จะไปอยู่ในแคชของแต่ละเซิร์ฟเวอร์ นี่เป็นโครงการที่ไม่มีประสิทธิภาพมาก แต่ก็ทำอะไรไม่ได้เลย
เพราะเหตุนี้ - เราไม่สามารถแบ่งเนื้อหาได้เนื่องจากเราไม่สามารถเลือกเซิร์ฟเวอร์เฉพาะสำหรับกลุ่มนี้ได้ - เซิร์ฟเวอร์เหล่านี้มี IP ทั่วไป ด้วยเหตุผลภายในบางประการที่เรามี ไม่สามารถติดตั้งเซิร์ฟเวอร์ดังกล่าวในภูมิภาคได้. พวกเขายืนอยู่ในเซนต์ปีเตอร์สเบิร์กเท่านั้น
ด้วยดวงอาทิตย์ เราได้เปลี่ยนระบบการเลือก ตอนนี้เรามี การกำหนดเส้นทางแบบ Anycast: การกำหนดเส้นทางแบบไดนามิก, Anycast, daemon ตรวจสอบตัวเอง แต่ละเซิร์ฟเวอร์มี IP ของตัวเอง แต่เป็นซับเน็ตทั่วไป ทุกอย่างได้รับการกำหนดค่าในลักษณะที่ว่าหากเซิร์ฟเวอร์ตัวใดตัวหนึ่งล้มเหลว การรับส่งข้อมูลจะกระจายไปยังเซิร์ฟเวอร์อื่นๆ ของกลุ่มเดียวกันโดยอัตโนมัติ ตอนนี้คุณสามารถเลือกเซิร์ฟเวอร์เฉพาะได้แล้ว ไม่มีแคชซ้ำซ้อนและไม่กระทบต่อความน่าเชื่อถือ
รองรับน้ำหนัก. ตอนนี้เราสามารถติดตั้งเครื่องจักรที่มีกำลังต่างกันได้ตามต้องการ และในกรณีที่เกิดปัญหาชั่วคราว ให้เปลี่ยนน้ำหนักของ "ดวงอาทิตย์" ที่ทำงานอยู่เพื่อลดภาระที่เครื่องเหล่านั้น "พัก" และเริ่มทำงานอีกครั้ง
การแบ่งส่วนตามรหัสเนื้อหา. เรื่องตลกเกี่ยวกับการแบ่งส่วน: โดยปกติแล้วเราจะแบ่งส่วนเนื้อหาเพื่อให้ผู้ใช้ที่แตกต่างกันไปที่ไฟล์เดียวกันผ่าน "ดวงอาทิตย์" เดียวกันเพื่อให้พวกเขามีแคชร่วมกัน
เราเพิ่งเปิดตัวแอปพลิเคชั่น “Clover” นี่คือแบบทดสอบออนไลน์ในการถ่ายทอดสด โดยพิธีกรถามคำถามและผู้ใช้ตอบแบบเรียลไทม์โดยเลือกตัวเลือก แอพมีการแชทที่ผู้ใช้สามารถแชทได้ สามารถเชื่อมต่อกับการออกอากาศพร้อมกันได้ มากกว่า 100 คน. พวกเขาทั้งหมดเขียนข้อความที่ส่งถึงผู้เข้าร่วมทุกคน และมีอวตารมาพร้อมกับข้อความ หากมีคน 100 คนมาเพื่ออวาตาร์ตัวเดียวใน "ดวงอาทิตย์" เดียว บางครั้งมันก็อาจกลิ้งอยู่หลังก้อนเมฆได้
เพื่อที่จะทนต่อการร้องขอไฟล์เดียวกันจำนวนมาก เนื้อหาบางประเภทจึงถูกเปิดใช้รูปแบบโง่ๆ ที่กระจายไฟล์ไปทั่ว "ดวงอาทิตย์" ที่มีอยู่ทั้งหมดในภูมิภาค
แสงอาทิตย์จากภายใน
ย้อนกลับพร็อกซีบน nginx แคชใน RAM หรือบนดิสก์ Optane/NVMe ที่รวดเร็ว ตัวอย่าง: http://sun4-2.userapi.com/c100500/path
— ลิงค์ไปยัง “ดวงอาทิตย์” ซึ่งตั้งอยู่ในภูมิภาคที่สี่ กลุ่มเซิร์ฟเวอร์ที่สอง โดยจะปิดไฟล์เส้นทางซึ่งจริงๆ แล้วอยู่บนเซิร์ฟเวอร์ 100500
แคช
เราเพิ่มอีกหนึ่งโหนดให้กับโครงร่างสถาปัตยกรรมของเรา - สภาพแวดล้อมการแคช
ด้านล่างนี้เป็นแผนผังเค้าโครง แคชระดับภูมิภาคมีประมาณ 20 คน เหล่านี้เป็นสถานที่ที่มีแคชและ "ดวงอาทิตย์" ซึ่งสามารถแคชการรับส่งข้อมูลผ่านตัวมันเองได้
นี่คือการแคชเนื้อหามัลติมีเดีย ไม่มีการจัดเก็บข้อมูลผู้ใช้ที่นี่ มีเพียงเพลง วิดีโอ รูปภาพ
เพื่อกำหนดภูมิภาคของผู้ใช้เรา เรารวบรวมคำนำหน้าเครือข่าย BGP ที่ประกาศในภูมิภาค. ในกรณีของทางเลือก เรายังต้องแยกวิเคราะห์ฐานข้อมูล geoip หากเราไม่พบ IP ด้วยคำนำหน้า เรากำหนดภูมิภาคตาม IP ของผู้ใช้. ในโค้ดนี้ เราสามารถดูหนึ่งหรือหลายภูมิภาคของผู้ใช้ - จุดเหล่านั้นที่เขาอยู่ใกล้ทางภูมิศาสตร์มากที่สุด
มันทำงานอย่างไร
เรานับความนิยมของไฟล์ตามภูมิภาค. มีแคชระดับภูมิภาคจำนวนหนึ่งที่ผู้ใช้ตั้งอยู่ และตัวระบุไฟล์ - เราใช้คู่นี้และเพิ่มคะแนนในการดาวน์โหลดแต่ละครั้ง
ในเวลาเดียวกันปีศาจ - บริการในภูมิภาค - มาที่ API เป็นครั้งคราวและพูดว่า: "ฉันเป็นแคชเช่นนี้ขอรายชื่อไฟล์ยอดนิยมในภูมิภาคของฉันที่ยังไม่มีให้ฉัน ” API จัดส่งไฟล์จำนวนมากโดยจัดเรียงตามการให้คะแนน daemon จะดาวน์โหลดไฟล์ พาพวกเขาไปยังภูมิภาค และส่งไฟล์จากที่นั่น นี่คือความแตกต่างพื้นฐานระหว่าง pu/pp และ Sun จากแคช โดยให้ไฟล์ผ่านตัวเองทันที แม้ว่าไฟล์นี้จะไม่ได้อยู่ในแคช และแคชจะดาวน์โหลดไฟล์ไปที่ตัวมันเองก่อน จากนั้นจึงเริ่มส่งคืน
ในกรณีนี้เราได้รับ เนื้อหาใกล้ชิดกับผู้ใช้มากขึ้น และกระจายโหลดเครือข่าย ตัวอย่างเช่น เฉพาะจากแคชมอสโกเท่านั้นที่เราแจกจ่ายมากกว่า 1 Tbit/s ในช่วงชั่วโมงเร่งด่วน
แต่มีปัญหา - เซิร์ฟเวอร์แคชไม่ใช่ยาง. สำหรับเนื้อหายอดนิยม บางครั้งอาจมีเครือข่ายไม่เพียงพอสำหรับเซิร์ฟเวอร์แยกต่างหาก แคชเซิร์ฟเวอร์ของเราอยู่ที่ 40-50 Gbit/s แต่มีเนื้อหาที่อุดตันช่องทางดังกล่าวโดยสิ้นเชิง เรากำลังมุ่งสู่การใช้พื้นที่จัดเก็บไฟล์ยอดนิยมมากกว่าหนึ่งสำเนาในภูมิภาค ฉันหวังว่าเราจะดำเนินการภายในสิ้นปีนี้
เราดูสถาปัตยกรรมทั่วไป
- เซิร์ฟเวอร์ด้านหน้าที่ยอมรับคำขอ
- แบ็กเอนด์ที่ประมวลผลคำขอ
- พื้นที่เก็บข้อมูลที่ถูกปิดโดยผู้รับมอบฉันทะสองประเภท
- แคชระดับภูมิภาค
มีอะไรหายไปจากแผนภาพนี้? แน่นอนว่าฐานข้อมูลที่เราเก็บข้อมูล
ฐานข้อมูลหรือเครื่องยนต์
เราเรียกพวกมันว่าไม่ใช่ฐานข้อมูล แต่เป็นเอ็นจิ้น - เอ็นจิ้นเพราะในทางปฏิบัติแล้วเราไม่มีฐานข้อมูลในแง่ที่ยอมรับกันโดยทั่วไป
นี่เป็นมาตรการที่จำเป็น. สิ่งนี้เกิดขึ้นเพราะในปี 2008-2009 เมื่อ VK ได้รับความนิยมเพิ่มขึ้นอย่างมาก โปรเจ็กต์นี้ทำงานได้บน MySQL และ Memcache ทั้งหมดและมีปัญหาเกิดขึ้น MySQL ชอบที่จะทำให้ไฟล์เสียหายและเสียหาย หลังจากนั้นจะไม่สามารถกู้คืนได้ และ Memcache ก็ค่อยๆ ลดประสิทธิภาพลงและต้องเริ่มต้นใหม่
ปรากฎว่าโปรเจ็กต์ที่ได้รับความนิยมมากขึ้นเรื่อยๆ มีพื้นที่เก็บข้อมูลถาวร ซึ่งทำให้ข้อมูลเสียหาย และแคชซึ่งทำให้ช้าลง ในสภาวะเช่นนี้ เป็นการยากที่จะพัฒนาโครงการที่กำลังเติบโต มีการตัดสินใจที่จะพยายามเขียนสิ่งสำคัญที่โครงการมุ่งเน้นไปที่จักรยานของเราเองใหม่
การแก้ปัญหาประสบความสำเร็จ. มีโอกาสที่จะทำเช่นนี้ เช่นเดียวกับความจำเป็นอย่างยิ่ง เนื่องจากในขณะนั้นยังไม่มีวิธีการปรับขนาดแบบอื่น ฐานข้อมูลมีไม่มากนัก NoSQL ยังไม่มี มีเพียง MySQL, Memcache, PostrgreSQL เท่านั้นเอง
การดำเนินงานที่เป็นสากล. การพัฒนานำโดยทีมนักพัฒนา C ของเรา และทุกอย่างก็ทำในลักษณะที่สอดคล้องกัน โดยไม่คำนึงถึงกลไก พวกมันทั้งหมดมีรูปแบบไฟล์เดียวกันโดยประมาณที่เขียนลงดิสก์ พารามิเตอร์การเรียกใช้งานเหมือนกัน สัญญาณที่ประมวลผลในลักษณะเดียวกัน และทำงานโดยประมาณเหมือนกันในกรณีของสถานการณ์และปัญหา Edge ด้วยการเติบโตของเครื่องยนต์ ทำให้ผู้ดูแลระบบสามารถใช้งานระบบได้สะดวก - ไม่ต้องดูแลรักษาสวนสัตว์ และพวกเขาต้องเรียนรู้วิธีใช้งานฐานข้อมูลบุคคลที่สามใหม่แต่ละฐานข้อมูล ซึ่งทำให้สามารถเพิ่มจำนวนได้อย่างรวดเร็วและสะดวก หมายเลขของพวกเขา
ประเภทของเครื่องยนต์
ทีมงานได้เขียนเครื่องยนต์จำนวนไม่น้อย นี่เป็นเพียงบางส่วนเท่านั้น: เพื่อน, คำใบ้, รูปภาพ, ipdb, ตัวอักษร, รายการ, บันทึก, memcached, meowdb, ข่าว, nostradamus, รูปภาพ, เพลย์ลิสต์, pmemcached, แซนด์บ็อกซ์, ค้นหา, ที่เก็บข้อมูล, ไลค์, งาน, ...
สำหรับแต่ละงานที่ต้องใช้โครงสร้างข้อมูลเฉพาะหรือประมวลผลคำขอที่ผิดปกติ ทีม C จะเขียนกลไกใหม่ ทำไมจะไม่ล่ะ.
เรามีเครื่องยนต์แยกต่างหาก memcachedซึ่งคล้ายกับของปกติ แต่มีสารพัดมากมายและไม่ทำให้ช้าลง ไม่ใช่ ClickHouse แต่ก็ใช้งานได้เช่นกัน จำหน่ายแยกต่างหาก pmแคช - เป็น memcached ถาวรซึ่งสามารถจัดเก็บข้อมูลบนดิสก์ได้ ยิ่งไปกว่านั้นยังพอดีกับ RAM เพื่อไม่ให้ข้อมูลสูญหายเมื่อรีสตาร์ท มีเอ็นจิ้นหลากหลายสำหรับงานแต่ละงาน: คิว, รายการ, ชุด - ทุกอย่างที่โปรเจ็กต์ของเราต้องการ
กลุ่ม
จากมุมมองของโค้ด ไม่จำเป็นต้องคิดถึงกลไกหรือฐานข้อมูลว่าเป็นกระบวนการ เอนทิตี หรืออินสแตนซ์ รหัสนี้ใช้งานได้เฉพาะกับคลัสเตอร์กับกลุ่มของเอ็นจิ้น - หนึ่งประเภทต่อคลัสเตอร์. สมมติว่ามีคลัสเตอร์ memcached - เป็นเพียงกลุ่มของเครื่อง
โค้ดไม่จำเป็นต้องทราบตำแหน่งทางกายภาพ ขนาด หรือจำนวนเซิร์ฟเวอร์เลย เขาไปที่คลัสเตอร์โดยใช้ตัวระบุที่แน่นอน
เพื่อให้สิ่งนี้ทำงานได้ คุณต้องเพิ่มเอนทิตีอีกหนึ่งรายการที่อยู่ระหว่างโค้ดและเอ็นจิ้น - หนังสือมอบฉันทะ.
พร็อกซี RPC
หนังสือมอบฉันทะ เชื่อมต่อรถบัสซึ่งทำงานเกือบทั้งไซต์ ในขณะเดียวกันเราก็มี ไม่มีการค้นพบบริการ — แต่มีการกำหนดค่าสำหรับพร็อกซีนี้แทน ซึ่งทราบตำแหน่งของคลัสเตอร์ทั้งหมดและส่วนย่อยทั้งหมดของคลัสเตอร์นี้ นี่คือสิ่งที่ผู้ดูแลระบบทำ
โปรแกรมเมอร์ไม่สนใจเลยว่าจะราคาเท่าไหร่ ที่ไหน และเท่าไหร่ พวกเขาแค่ไปที่คลัสเตอร์เท่านั้น สิ่งนี้ช่วยให้เราได้มาก เมื่อได้รับคำขอ พร็อกซีจะเปลี่ยนเส้นทางคำขอโดยรู้ว่าจะกำหนดสิ่งนี้เองที่ไหน
ในกรณีนี้ พร็อกซีคือจุดป้องกันความล้มเหลวของบริการ หากกลไกบางตัวทำงานช้าลงหรือขัดข้อง พร็อกซีจะเข้าใจสิ่งนี้และตอบสนองต่อฝั่งไคลเอ็นต์ตามนั้น สิ่งนี้ช่วยให้คุณสามารถลบการหมดเวลา - รหัสไม่รอให้เครื่องยนต์ตอบสนอง แต่เข้าใจว่ามันไม่ทำงานและจำเป็นต้องทำงานแตกต่างออกไป ต้องเตรียมรหัสเนื่องจากฐานข้อมูลไม่ทำงานเสมอไป
การใช้งานเฉพาะ
บางครั้งเรายังต้องการโซลูชันที่ไม่ได้มาตรฐานมาเป็นเครื่องมือ ในเวลาเดียวกัน มีการตัดสินใจว่าจะไม่ใช้ rpc-proxy สำเร็จรูปของเราซึ่งสร้างขึ้นสำหรับเอ็นจิ้นของเราโดยเฉพาะ แต่เพื่อสร้างพร็อกซีแยกต่างหากสำหรับงาน
สำหรับ MySQL ซึ่งเรายังมีที่นี่และที่นั่น เราใช้ db-proxy และสำหรับ ClickHouse - บ้านลูกแมว.
มันทำงานโดยทั่วไปเช่นนี้ มีเซิร์ฟเวอร์บางตัว มันรัน kPHP, Go, Python โดยทั่วไปคือโค้ดใดๆ ที่สามารถใช้โปรโตคอล RPC ของเราได้ รหัสทำงานภายในเครื่องบนพร็อกซี RPC - แต่ละเซิร์ฟเวอร์ที่มีรหัสอยู่จะเรียกใช้พร็อกซีในเครื่องของตัวเอง เมื่อมีการร้องขอ พร็อกซีจะเข้าใจว่าจะต้องไปที่ไหน
หากกลไกหนึ่งต้องการไปยังอีกเครื่องหนึ่ง แม้ว่าจะเป็นเพื่อนบ้านก็ตาม จะต้องผ่านพร็อกซี เนื่องจากเพื่อนบ้านอาจอยู่ในศูนย์ข้อมูลอื่น เครื่องยนต์ไม่ควรพึ่งพาการรู้ตำแหน่งของสิ่งอื่นใดนอกจากตัวมันเอง - นี่คือวิธีแก้ปัญหามาตรฐานของเรา แต่แน่นอนว่ามีข้อยกเว้น :)
ตัวอย่างของ TL-scheme ตามที่เครื่องยนต์ทั้งหมดทำงาน
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;
นี่คือโปรโตคอลไบนารี่ ซึ่งเป็นอะนาล็อกที่ใกล้เคียงที่สุด โปรโตบุฟ สคีมากำหนดฟิลด์ตัวเลือก ประเภทที่ซับซ้อน - ส่วนขยายของสเกลาร์ในตัว และการสืบค้น ทุกอย่างทำงานตามโปรโตคอลนี้
RPC ผ่าน TL ผ่าน TCP/UDP… UDP?
เรามีโปรโตคอล RPC สำหรับดำเนินการคำขอกลไกที่ทำงานบนแผน TL ทั้งหมดนี้ทำงานผ่านการเชื่อมต่อ TCP/UDP TCP เป็นสิ่งที่เข้าใจได้ แต่ทำไมเราถึงต้องการ UDP บ่อยครั้ง
UDP ช่วยได้ หลีกเลี่ยงปัญหาการเชื่อมต่อจำนวนมากระหว่างเซิร์ฟเวอร์. หากแต่ละเซิร์ฟเวอร์มีพร็อกซี RPC และโดยทั่วไปสามารถไปที่กลไกใดก็ได้ แสดงว่ามีการเชื่อมต่อ TCP นับหมื่นต่อเซิร์ฟเวอร์ มีภาระแต่ก็ไม่มีประโยชน์ ในกรณีของ UDP ปัญหานี้ไม่มีอยู่
ไม่มีการจับมือ TCP ซ้ำซ้อน. นี่เป็นปัญหาทั่วไป: เมื่อมีการเปิดตัวเอ็นจิ้นใหม่หรือเซิร์ฟเวอร์ใหม่ การเชื่อมต่อ TCP จำนวนมากจะถูกสร้างขึ้นพร้อมกัน สำหรับคำขอขนาดเล็กน้ำหนักเบา เช่น เพย์โหลด UDP การสื่อสารทั้งหมดระหว่างโค้ดและกลไกจะเป็น UDP สองแพ็กเก็ต: ตัวหนึ่งบินไปในทิศทางเดียว ตัวที่สองบินไปในทิศทางอื่น ไปกลับหนึ่งครั้ง - และรหัสก็ได้รับการตอบสนองจากเครื่องยนต์โดยไม่ต้องจับมือกัน
ใช่แล้ว ทุกอย่างมันใช้งานได้ โดยมีเปอร์เซ็นต์การสูญเสียแพ็กเก็ตน้อยมาก. โปรโตคอลรองรับการส่งสัญญาณซ้ำและการหมดเวลา แต่ถ้าเราสูญเสียไปมาก เราก็จะได้เกือบ TCP ซึ่งไม่เป็นประโยชน์ เราไม่ขับเคลื่อน UDP ข้ามมหาสมุทร
เรามีเซิร์ฟเวอร์ดังกล่าวหลายพันเครื่อง และรูปแบบก็เหมือนกัน: มีการติดตั้งแพ็กของเอ็นจิ้นบนเซิร์ฟเวอร์จริงแต่ละเครื่อง ส่วนใหญ่จะเป็นแบบเธรดเดียวเพื่อให้ทำงานเร็วที่สุดเท่าที่จะเป็นไปได้โดยไม่ถูกบล็อก และแบ่งส่วนเป็นโซลูชันแบบเธรดเดียว ในเวลาเดียวกัน เราไม่มีอะไรน่าเชื่อถือไปกว่าเครื่องมือเหล่านี้ และให้ความสนใจอย่างมากกับการจัดเก็บข้อมูลแบบถาวร
การจัดเก็บข้อมูลอย่างต่อเนื่อง
เครื่องยนต์เขียน binlogs. binlog คือไฟล์ที่ส่วนท้ายของเหตุการณ์สำหรับการเปลี่ยนแปลงสถานะหรือข้อมูลที่ถูกเพิ่มเข้าไป ในโซลูชันต่าง ๆ มันถูกเรียกแตกต่างกัน: บันทึกไบนารี
เพื่อป้องกันไม่ให้เครื่องยนต์อ่าน binlog ทั้งหมดซ้ำเป็นเวลาหลายปีเมื่อสตาร์ทเครื่องยนต์ใหม่ เครื่องยนต์จะเขียน สแนปชอต - สถานะปัจจุบัน. หากจำเป็น ให้อ่านจากเอกสารก่อน จากนั้นจึงอ่านจาก Binlog ให้จบ binlogs ทั้งหมดถูกเขียนในรูปแบบไบนารี่เดียวกัน - ตามแผน TL เพื่อให้ผู้ดูแลระบบสามารถจัดการพวกมันได้อย่างเท่าเทียมกันด้วยเครื่องมือของพวกเขา ไม่จำเป็นต้องมีสแนปชอตดังกล่าว มีส่วนหัวทั่วไปที่ระบุว่าสแน็ปช็อตของใครเป็น int ความมหัศจรรย์ของเครื่องยนต์ และตัวถังไหนไม่สำคัญสำหรับใครเลย นี่เป็นปัญหากับเอ็นจิ้นที่บันทึกสแนปช็อต
ฉันจะอธิบายหลักการทำงานอย่างรวดเร็ว มีเซิร์ฟเวอร์ที่เครื่องยนต์ทำงาน เขาเปิด Binlog เปล่าอันใหม่เพื่อเขียนและเขียนกิจกรรมเพื่อเปลี่ยนแปลง
เมื่อถึงจุดหนึ่ง เขาตัดสินใจถ่ายภาพตัวเองหรือรับสัญญาณ เซิร์ฟเวอร์สร้างไฟล์ใหม่ เขียนสถานะทั้งหมดลงไป เพิ่มขนาด binlog ปัจจุบันต่อท้ายไฟล์ และเขียนต่อไป ไม่ได้สร้าง binlog ใหม่
เมื่อถึงจุดหนึ่ง เมื่อสตาร์ทเครื่องยนต์ใหม่ จะมีทั้ง Binlog และ Snapshot บนดิสก์ เครื่องยนต์จะอ่านสแน็ปช็อตทั้งหมดและเพิ่มสถานะ ณ จุดหนึ่ง
อ่านตำแหน่งที่อยู่ในขณะที่สร้างสแน็ปช็อตและขนาดของ binlog
อ่านส่วนท้ายของ binlog เพื่อรับสถานะปัจจุบันและเขียนเหตุการณ์ต่อไปต่อไป นี่เป็นรูปแบบง่ายๆ เครื่องยนต์ทั้งหมดของเราทำงานตามนั้น
การจำลองข้อมูล
ส่งผลให้มีการจำลองข้อมูลในตัวเรา ตามคำสั่ง — เราเขียนใน binlog ว่าไม่มีการเปลี่ยนแปลงหน้าใดๆ แต่กล่าวคือ เปลี่ยนแปลงคำขอ. คล้ายกันมากกับสิ่งที่มาจากเครือข่าย ปรับเปลี่ยนเพียงเล็กน้อยเท่านั้น
รูปแบบเดียวกันนี้ใช้ไม่เพียงแต่สำหรับการจำลองเท่านั้น แต่ยังใช้ด้วย เพื่อสร้างข้อมูลสำรอง. เรามีเอ็นจิ้น - ผู้เชี่ยวชาญด้านการเขียนที่เขียนลงใน binlog ในสถานที่อื่นๆ ที่ผู้ดูแลระบบตั้งค่าไว้ binlog นี้จะถูกคัดลอก เพียงเท่านี้ เราก็มีข้อมูลสำรองแล้ว
หากมีความจำเป็น การจำลองการอ่านเพื่อลดภาระการอ่าน CPU เอ็นจิ้นการอ่านจะถูกเปิดใช้งาน ซึ่งจะอ่านจุดสิ้นสุดของ binlog และดำเนินการคำสั่งเหล่านี้ในเครื่อง
ความล่าช้าที่นี่มีน้อยมาก และเป็นไปได้ที่จะทราบว่าแบบจำลองล่าช้ากว่าต้นแบบมากน้อยเพียงใด
การแบ่งส่วนข้อมูลในพร็อกซี RPC
การแบ่งส่วนทำงานอย่างไร พร็อกซีเข้าใจได้อย่างไรว่าคลัสเตอร์คลัสเตอร์ใดที่จะส่งไป รหัสไม่ได้บอกว่า: “ส่ง 15 ชิ้นส่วน!” - ไม่ สิ่งนี้จะกระทำโดยผู้รับมอบฉันทะ
รูปแบบที่ง่ายที่สุดคืออันดับแรก — หมายเลขแรกในคำขอ
get(photo100_500) => 100 % N.
นี่คือตัวอย่างสำหรับโปรโตคอลข้อความ memcached แบบธรรมดา แต่แน่นอนว่า การสืบค้นอาจซับซ้อนและมีโครงสร้างได้ ตัวอย่างใช้ตัวเลขแรกในแบบสอบถามและส่วนที่เหลือเมื่อหารด้วยขนาดคลัสเตอร์
สิ่งนี้มีประโยชน์เมื่อเราต้องการมีข้อมูลท้องถิ่นของเอนทิตีเดียว สมมติว่า 100 เป็นรหัสผู้ใช้หรือกลุ่ม และเราต้องการให้ข้อมูลทั้งหมดของเอนทิตีหนึ่งอยู่ในส่วนเดียวสำหรับการสืบค้นที่ซับซ้อน
หากเราไม่สนใจว่าคำขอจะกระจายไปทั่วทั้งคลัสเตอร์อย่างไร ก็ยังมีอีกทางเลือกหนึ่ง - แฮชทั้งส่วน.
hash(photo100_500) => 3539886280 % N
เรายังได้รับแฮช ส่วนที่เหลือของการหาร และหมายเลขชาร์ดอีกด้วย
ตัวเลือกทั้งสองนี้จะใช้งานได้ก็ต่อเมื่อเราเตรียมพร้อมสำหรับข้อเท็จจริงที่ว่าเมื่อเราเพิ่มขนาดของคลัสเตอร์ เราจะแบ่งหรือเพิ่มขึ้นหลายเท่า ตัวอย่างเช่น เรามี 16 ชิ้นส่วน เรามีไม่เพียงพอ เราต้องการมากกว่านี้ - เราสามารถรับ 32 ชิ้นได้อย่างปลอดภัยโดยไม่ต้องหยุดทำงาน หากเราต้องการเพิ่มไม่ใช่ทวีคูณ จะมีการหยุดทำงานเนื่องจากเราไม่สามารถแยกทุกอย่างได้อย่างแม่นยำโดยไม่สูญเสีย ตัวเลือกเหล่านี้มีประโยชน์แต่ก็ไม่เสมอไป
หากเราต้องการเพิ่มหรือลบเซิร์ฟเวอร์ตามจำนวนที่ต้องการ เราจะใช้ การแฮชอย่างต่อเนื่องบนวงแหวน a la Ketama. แต่ในขณะเดียวกัน เราก็สูญเสียตำแหน่งของข้อมูลไปโดยสิ้นเชิง เราต้องรวมคำขอเข้ากับคลัสเตอร์เพื่อให้แต่ละชิ้นส่งคืนการตอบกลับเล็กๆ ของตัวเอง จากนั้นจึงรวมการตอบกลับไปยังพร็อกซี
มีคำขอที่เจาะจงเป็นพิเศษ มีลักษณะดังนี้: พร็อกซี RPC ได้รับการร้องขอ กำหนดว่าจะไปที่คลัสเตอร์ใด และกำหนดส่วนแบ่งข้อมูล จากนั้นจะมีผู้เชี่ยวชาญด้านการเขียน หรือหากคลัสเตอร์มีการสนับสนุนเรพลิกา คลัสเตอร์ก็จะส่งไปยังเรพลิกาตามต้องการ ทั้งหมดนี้ทำโดยผู้รับมอบฉันทะ
บันทึก
เราเขียนบันทึกได้หลายวิธี สิ่งที่ชัดเจนและเรียบง่ายที่สุดคือ เขียนบันทึกลงใน memcache.
ring-buffer: prefix.idx = line
มีคำนำหน้าคีย์ - ชื่อของบันทึก บรรทัด และขนาดของบันทึกนี้ - จำนวนบรรทัด เราใช้ตัวเลขสุ่มตั้งแต่ 0 ถึงจำนวนบรรทัดลบ 1 คีย์ใน memcache คือคำนำหน้าที่ต่อกับตัวเลขสุ่มนี้ เราบันทึกบรรทัดบันทึกและเวลาปัจจุบันเป็นค่า
เมื่อจำเป็นต้องอ่านบันทึก เราก็ดำเนินการ รับหลาย คีย์ทั้งหมดจัดเรียงตามเวลา และรับบันทึกการผลิตแบบเรียลไทม์ แบบแผนจะใช้เมื่อคุณต้องการดีบักบางอย่างในการใช้งานจริงแบบเรียลไทม์ โดยไม่ทำลายสิ่งใด โดยไม่หยุดหรืออนุญาตให้มีการรับส่งข้อมูลไปยังเครื่องอื่น แต่บันทึกนี้อยู่ได้ไม่นาน
เพื่อการจัดเก็บบันทึกที่เชื่อถือได้ เรามีเครื่องมือ บันทึกเครื่องยนต์. นี่คือเหตุผลว่าทำไมจึงถูกสร้างขึ้นและมีการใช้กันอย่างแพร่หลายในกลุ่มจำนวนมาก คลัสเตอร์ที่ใหญ่ที่สุดที่ฉันรู้จักจัดเก็บบันทึกที่แพ็กไว้ 600 TB
เครื่องยนต์เก่ามากมีกระจุกอายุ6-7ปีแล้ว มีปัญหาเกิดขึ้นซึ่งเรากำลังพยายามแก้ไข เช่น เราเริ่มใช้ ClickHouse เพื่อจัดเก็บบันทึก
การรวบรวมบันทึกใน ClickHouse
แผนภาพนี้แสดงวิธีที่เราเดินเข้าไปในเครื่องยนต์ของเรา
มีโค้ดที่ส่งผ่าน RPC ภายในเครื่องไปยังพร็อกซี RPC และเข้าใจว่าจะไปที่เอ็นจิ้นที่ไหน หากเราต้องการเขียนบันทึกใน ClickHouse เราจำเป็นต้องเปลี่ยนสองส่วนในโครงการนี้:
- แทนที่เครื่องยนต์ด้วย ClickHouse
- แทนที่พร็อกซี RPC ซึ่งไม่สามารถเข้าถึง ClickHouse ด้วยวิธีแก้ปัญหาบางอย่างที่สามารถทำได้และผ่าน RPC
เอ็นจิ้นนั้นเรียบง่าย - เราแทนที่มันด้วยเซิร์ฟเวอร์หรือคลัสเตอร์ของเซิร์ฟเวอร์ด้วย ClickHouse
และเพื่อไปที่ ClickHouse เราก็ทำ บ้านลูกแมว. หากเราไปจาก KittyHouse ไปยัง ClickHouse โดยตรง มันจะไม่รับมือ แม้ว่าจะไม่มีคำขอใดๆ มันก็เพิ่มขึ้นจากการเชื่อมต่อ HTTP ของเครื่องจำนวนมาก เพื่อให้รูปแบบการทำงานบนเซิร์ฟเวอร์ที่มี ClickHouse พร็อกซีย้อนกลับในเครื่องถูกยกขึ้นซึ่งเขียนในลักษณะที่สามารถทนต่อปริมาณการเชื่อมต่อที่ต้องการได้ นอกจากนี้ยังสามารถบัฟเฟอร์ข้อมูลภายในตัวเองได้อย่างน่าเชื่อถืออีกด้วย
บางครั้งเราไม่ต้องการใช้แผน RPC ในโซลูชันที่ไม่ได้มาตรฐาน เช่น ใน nginx ดังนั้น KittyHouse จึงมีความสามารถในการรับบันทึกผ่าน UDP
หากผู้ส่งและผู้รับบันทึกทำงานบนเครื่องเดียวกัน ความน่าจะเป็นที่จะสูญเสียแพ็กเก็ต UDP ภายในโฮสต์ภายในเครื่องจะค่อนข้างต่ำ เพื่อเป็นการประนีประนอมระหว่างความจำเป็นในการใช้ RPC ในโซลูชันของบริษัทอื่นและความน่าเชื่อถือ เราเพียงแต่ใช้การส่ง UDP เราจะกลับมาที่โครงการนี้ในภายหลัง
การตรวจสอบ
เรามีบันทึกสองประเภท: บันทึกที่รวบรวมโดยผู้ดูแลระบบบนเซิร์ฟเวอร์และบันทึกที่เขียนโดยนักพัฒนาจากโค้ด ซึ่งสอดคล้องกับเมตริก XNUMX ประเภท: ระบบและผลิตภัณฑ์.
ตัวชี้วัดของระบบ
มันใช้งานได้บนเซิร์ฟเวอร์ทั้งหมดของเรา
ตัวชี้วัดผลิตภัณฑ์
เพื่อความสะดวกเราได้เขียนอะไรมากมาย ตัวอย่างเช่นมีชุดฟังก์ชันธรรมดาที่ให้คุณเขียนค่า Counts, UniqueCounts ลงในสถิติซึ่งจะถูกส่งไปที่ไหนสักแห่งเพิ่มเติม
statlogsCountEvent ( ‘stat_name’, $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid, $key1, $key2, …)
statlogsValuetEvent ( ‘stat_name’, $value, $key1, $key2, …)
$stats = statlogsStatData($params)
ต่อจากนั้น เราสามารถใช้ตัวกรองการเรียงลำดับและการจัดกลุ่ม และทำทุกอย่างที่เราต้องการจากสถิติ - สร้างกราฟ กำหนดค่า Watchdogs
เราเขียนมาก เมตริกมากมาย จำนวนเหตุการณ์อยู่ที่ 600 พันล้านถึง 1 ล้านล้านต่อวัน อย่างไรก็ตามเราต้องการเก็บไว้ อย่างน้อยสองสามปีเพื่อทำความเข้าใจแนวโน้มของเมตริก การรวมทุกอย่างเข้าด้วยกันเป็นปัญหาใหญ่ที่เรายังไม่ได้แก้ไข ฉันจะบอกคุณว่ามันทำงานอย่างไรในช่วงไม่กี่ปีที่ผ่านมา
เรามีฟังก์ชันที่เขียนหน่วยวัดเหล่านี้ ไปยัง memcache ในเครื่องเพื่อลดจำนวนรายการ ครั้งหนึ่งในช่วงเวลาสั้นๆ ที่เปิดตัวในประเทศ สถิติภูต รวบรวมบันทึกทั้งหมด ถัดไป ปีศาจจะรวมหน่วยวัดออกเป็นเซิร์ฟเวอร์สองชั้น นักสะสมไม้ซุงซึ่งรวบรวมสถิติจากกลุ่มเครื่องของเราเพื่อไม่ให้เลเยอร์ที่อยู่ด้านหลังเครื่องเหล่านั้นตาย
หากจำเป็น เราสามารถเขียนถึงผู้รวบรวมบันทึกได้โดยตรง
แต่การเขียนจากโค้ดโดยตรงไปยังตัวรวบรวม โดยข้าม stas-daemom นั้นเป็นโซลูชันที่ปรับขนาดได้ไม่ดี เนื่องจากจะเพิ่มภาระให้กับตัวรวบรวม วิธีแก้ปัญหานี้เหมาะสมเฉพาะในกรณีที่เราไม่สามารถเพิ่ม memcache stats-daemon บนเครื่องได้ด้วยเหตุผลบางประการ หรือเกิดข้อผิดพลาดและเราดำเนินการโดยตรง
ถัดไป นักสะสมบันทึกจะรวมสถิติเข้าด้วยกัน เหมียวDB - นี่คือฐานข้อมูลของเรา ซึ่งสามารถจัดเก็บหน่วยวัดได้เช่นกัน
จากนั้นเราสามารถทำการเลือกไบนารี่ "ใกล้ SQL" จากโค้ดได้
การทดลอง
ในช่วงฤดูร้อนปี 2018 เรามีแฮ็กกาธอนภายใน และมีแนวคิดเกิดขึ้นเพื่อพยายามแทนที่ส่วนสีแดงของไดอะแกรมด้วยสิ่งที่สามารถจัดเก็บหน่วยวัดใน ClickHouse เรามีบันทึกใน ClickHouse - ทำไมไม่ลองล่ะ?
เรามีโครงการที่เขียนบันทึกผ่าน KittyHouse
เราตัดสินใจแล้ว เพิ่ม "*House" อีกอันลงในไดอะแกรมซึ่งจะได้รับหน่วยวัดในรูปแบบตามที่โค้ดของเราเขียนผ่าน UDP จากนั้น *House นี้จะเปลี่ยนพวกมันให้กลายเป็นส่วนแทรก เหมือนท่อนไม้ ซึ่ง KittyHouse เข้าใจ เขาสามารถส่งบันทึกเหล่านี้ไปยัง ClickHouse ได้อย่างสมบูรณ์แบบ ซึ่งควรจะสามารถอ่านได้
รูปแบบที่มีฐานข้อมูล memcache, stats-daemon และ logs-collectors จะถูกแทนที่ด้วยฐานข้อมูลนี้
รูปแบบที่มีฐานข้อมูล memcache, stats-daemon และ logs-collectors จะถูกแทนที่ด้วยฐานข้อมูลนี้
- มีการจัดส่งจากโค้ดที่นี่ ซึ่งเขียนขึ้นในเครื่องใน StatsHouse
- StatsHouse เขียนการวัด UDP ซึ่งแปลงเป็นส่วนแทรก SQL แล้วเป็น KittenHouse เป็นกลุ่ม
- KittyHouse ส่งพวกมันไปที่ ClickHouse
- หากเราต้องการอ่าน เราก็อ่านได้โดยข้าม StatsHouse - โดยตรงจาก ClickHouse โดยใช้ SQL ปกติ
ยังอยู่มั้ย การทดลองแต่เราชอบที่มันปรากฏออกมา หากเราแก้ไขปัญหาเกี่ยวกับโครงการนี้แล้ว บางทีเราอาจจะเปลี่ยนไปใช้โครงการนี้โดยสมบูรณ์ โดยส่วนตัวแล้วฉันหวังเช่นนั้น
โครงการนี้ ไม่ประหยัดเหล็ก. จำเป็นต้องใช้เซิร์ฟเวอร์น้อยลง ไม่จำเป็นต้องใช้ stats-daemons และ logs-collector ในเครื่อง แต่ ClickHouse ต้องการเซิร์ฟเวอร์ที่ใหญ่กว่าเซิร์ฟเวอร์ในรูปแบบปัจจุบัน จำเป็นต้องใช้เซิร์ฟเวอร์น้อยลง แต่ต้องมีราคาแพงกว่าและมีประสิทธิภาพมากกว่า.
ปรับใช้
ก่อนอื่น มาดูการติดตั้ง PHP กันก่อน เรากำลังพัฒนาใน คอมไพล์: ใช้ GitLab и TeamCity สำหรับการปรับใช้ สาขาการพัฒนาจะผสานเข้ากับสาขาหลัก จากสาขาหลักสำหรับการทดสอบจะรวมเข้าเป็นการแสดงระยะ และจากการแสดงละครเป็นการใช้งานจริง
ก่อนที่จะปรับใช้ สาขาการผลิตปัจจุบันและสาขาก่อนหน้าจะถูกนำไปใช้ และไฟล์ diff จะได้รับการพิจารณาในสาขาเหล่านั้น - การเปลี่ยนแปลง: สร้าง, ลบ, เปลี่ยนแปลง การเปลี่ยนแปลงนี้ถูกบันทึกไว้ใน binlog ของกลไกการคัดลอกแบบพิเศษ ซึ่งสามารถทำซ้ำการเปลี่ยนแปลงกับฟลีตเซิร์ฟเวอร์ทั้งหมดของเราได้อย่างรวดเร็ว สิ่งที่ใช้ในที่นี้ไม่ใช่การคัดลอกโดยตรงแต่ การจำลองการนินทาเมื่อเซิร์ฟเวอร์เครื่องหนึ่งส่งการเปลี่ยนแปลงไปยังเพื่อนบ้านที่ใกล้ที่สุด การเปลี่ยนแปลงไปยังเพื่อนบ้าน และอื่นๆ ซึ่งจะทำให้คุณสามารถอัปเดตโค้ดได้ภายในเวลาหลายสิบและหน่วยวินาทีทั่วทั้งฟลีต เมื่อการเปลี่ยนแปลงไปถึงแบบจำลองในเครื่อง ระบบจะใช้โปรแกรมแก้ไขเหล่านี้กับตัวจำลอง ระบบไฟล์โลคัล. การย้อนกลับยังดำเนินการตามรูปแบบเดียวกัน
เรายังปรับใช้ kPHP เป็นจำนวนมากและยังมีการพัฒนาในตัวมันเองด้วย คอมไพล์ ตามแผนภาพด้านบน ตั้งแต่นี้เป็นต้นมา ไบนารีเซิร์ฟเวอร์ HTTPจากนั้นเราไม่สามารถสร้างความแตกต่างได้ - ไบนารี่รีลีสมีน้ำหนักหลายร้อย MB ดังนั้นจึงมีตัวเลือกอื่น - เวอร์ชันนี้เขียนถึง binlog คัดลอกอย่างรวดเร็ว. ในแต่ละบิลด์มันจะเพิ่มขึ้น และระหว่างการย้อนกลับก็จะเพิ่มขึ้นเช่นกัน เวอร์ชัน จำลองแบบไปยังเซิร์ฟเวอร์. การคัดลอกในเครื่องจะเห็นว่าเวอร์ชันใหม่ได้เข้าสู่ binlog และด้วยการจำลองแบบซุบซิบเดียวกัน พวกเขาใช้ไบนารีเวอร์ชันล่าสุดสำหรับตัวเอง โดยไม่ทำให้เซิร์ฟเวอร์หลักของเราเหนื่อย แต่กระจายโหลดไปทั่วเครือข่ายอย่างระมัดระวัง อะไรตามมา. การเปิดตัวใหม่อย่างสง่างาม สำหรับเวอร์ชั่นใหม่
สำหรับเครื่องยนต์ของเรา ซึ่งโดยพื้นฐานแล้วก็คือไบนารี่ รูปแบบจะคล้ายกันมาก:
- สาขา git master;
- ไบนารีเข้า หญิงที่เข้าสังคมครั้งแรก;
- เวอร์ชันนี้เขียนเป็น binlog copyfast;
- จำลองแบบไปยังเซิร์ฟเวอร์
- เซิร์ฟเวอร์ดึง .dep ใหม่ออกมา
- dpkg -i;
- การเปิดตัวเวอร์ชันใหม่อย่างสง่างาม
ข้อแตกต่างก็คือไบนารีของเราถูกบรรจุอยู่ในไฟล์เก็บถาวร หญิงที่เข้าสังคมครั้งแรกและเมื่อปั๊มออกมา dpkg -i จะถูกวางไว้บนระบบ เหตุใด kPHP จึงปรับใช้เป็นไบนารี่ และเอ็นจิ้นถูกปรับใช้เป็น dpkg มันเกิดขึ้นอย่างนั้น มันใช้งานได้ - อย่าแตะต้องมัน
ลิงค์ที่เป็นประโยชน์
- รายงานโดย Anton Kiryushkin
“ ผู้ดูแลระบบ Vkontakte ยังไง?" พร้อมรายละเอียดเกี่ยวกับการลอกเลียนแบบและการนินทา - รายงานโดย ยูริ นาเร็ตดินอฟ
“VK แทรกข้อมูลลงใน CLickHouse จากเซิร์ฟเวอร์นับหมื่นได้อย่างไร” . - รายงานของฉัน
“สถาปัตยกรรมของโครงการที่กำลังเติบโตโดยใช้ตัวอย่างของ VKontakte” แต่จากมุมมองของการพัฒนา ไม่ใช่ฮาร์ดแวร์
Alexey Akulovich เป็นหนึ่งในผู้ที่ช่วยเหลือในฐานะส่วนหนึ่งของคณะกรรมการโครงการ
PHP รัสเซีย ในวันที่ 17 พฤษภาคม จะกลายเป็นงานที่ใหญ่ที่สุดสำหรับนักพัฒนา PHP ในช่วงเวลาที่ผ่านมา ดูสิว่าเรามีพีซีเจ๋งๆ อะไรสักอย่างลำโพง (สองคนกำลังพัฒนาแกน PHP!) - ดูเหมือนเป็นสิ่งที่คุณไม่ควรพลาดหากคุณเขียน PHP
ที่มา: will.com