“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

ตั้งแต่ปี 2019 เป็นต้นมา รัสเซียมีกฎหมายเกี่ยวกับการบังคับติดฉลาก กฎหมายใช้ไม่ได้กับสินค้าทุกกลุ่ม และวันที่การบังคับใช้การติดฉลากบังคับสำหรับกลุ่มผลิตภัณฑ์จะแตกต่างกัน ยาสูบ รองเท้า และยารักษาโรคเป็นสิ่งแรกที่ต้องมีการติดฉลากบังคับ ส่วนผลิตภัณฑ์อื่นๆ จะถูกเพิ่มเติมในภายหลัง เช่น น้ำหอม สิ่งทอ และนม นวัตกรรมด้านกฎหมายนี้กระตุ้นให้เกิดการพัฒนาโซลูชันไอทีใหม่ ๆ ซึ่งจะทำให้สามารถติดตามห่วงโซ่ชีวิตทั้งหมดของผลิตภัณฑ์ตั้งแต่การผลิตไปจนถึงการซื้อโดยผู้บริโภคขั้นสุดท้าย ไปจนถึงผู้เข้าร่วมทั้งหมดในกระบวนการ: ทั้งรัฐเองและทุกองค์กรที่ขายสินค้าด้วย การติดฉลากบังคับ

ใน X5 ระบบที่จะติดตามสินค้าที่มีฉลากและแลกเปลี่ยนข้อมูลกับรัฐและซัพพลายเออร์เรียกว่า "Marcus" เราจะมาบอกคุณกันว่าใครเป็นผู้พัฒนามันอย่างไรและอย่างไร เทคโนโลยีของมันคืออะไร และทำไมเราถึงมีบางอย่างที่น่าภาคภูมิใจ

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

โหลดสูงจริง

“Marcus” แก้ปัญหาได้มากมาย ปัญหาหลักคือการบูรณาการการทำงานร่วมกันระหว่างระบบข้อมูล X5 และระบบข้อมูลสถานะสำหรับผลิตภัณฑ์ที่มีป้ายกำกับ (GIS MP) เพื่อติดตามความเคลื่อนไหวของผลิตภัณฑ์ที่มีป้ายกำกับ แพลตฟอร์มยังจัดเก็บรหัสการติดฉลากทั้งหมดที่เราได้รับและประวัติทั้งหมดของการเคลื่อนไหวของรหัสเหล่านี้ในวัตถุต่างๆ และช่วยกำจัดการคัดเกรดผลิตภัณฑ์ที่มีฉลากใหม่ จากตัวอย่างผลิตภัณฑ์ยาสูบซึ่งรวมอยู่ในกลุ่มสินค้าที่มีฉลากกลุ่มแรก บุหรี่เพียงรถบรรทุกคันเดียวบรรจุได้ประมาณ 600 ซอง ซึ่งแต่ละบุหรี่มีรหัสเฉพาะของตัวเอง และงานของระบบของเราคือการติดตามและตรวจสอบความถูกต้องตามกฎหมายของการเคลื่อนย้ายของแต่ละแพ็คดังกล่าวระหว่างคลังสินค้าและร้านค้า และในท้ายที่สุดก็ตรวจสอบการยอมรับการขายให้กับผู้ซื้อปลายทาง และเราบันทึกธุรกรรมเงินสดประมาณ 000 รายการต่อชั่วโมง และเรายังต้องบันทึกด้วยว่าแต่ละแพ็คดังกล่าวเข้ามาในร้านได้อย่างไร ดังนั้น เมื่อคำนึงถึงการเคลื่อนไหวทั้งหมดระหว่างวัตถุ เราจึงคาดว่าจะมีบันทึกนับหมื่นล้านรายการต่อปี

ทีมเอ็ม

แม้ว่าข้อเท็จจริงที่ว่า Marcus จะถือเป็นโครงการภายใน X5 แต่โครงการนี้กำลังดำเนินการโดยใช้แนวทางผลิตภัณฑ์ ทีมงานทำงานตาม Scrum โครงการเริ่มต้นเมื่อฤดูร้อนที่แล้ว แต่ผลลัพธ์แรกเกิดขึ้นในเดือนตุลาคมเท่านั้น ทีมงานของเราได้ประกอบกันเสร็จสมบูรณ์แล้ว สถาปัตยกรรมระบบได้รับการพัฒนา และซื้ออุปกรณ์แล้ว ขณะนี้ทีมงานมี 16 คน โดย XNUMX คนเกี่ยวข้องกับการพัฒนาแบ็กเอนด์และส่วนหน้า และ XNUMX คนเกี่ยวข้องกับการวิเคราะห์ระบบ มีอีกหกคนที่มีส่วนร่วมในการทดสอบด้วยตนเอง โหลด การทดสอบอัตโนมัติ และการบำรุงรักษาผลิตภัณฑ์ นอกจากนี้เรายังมีผู้เชี่ยวชาญด้าน SRE

ไม่ใช่แค่นักพัฒนาเท่านั้นที่เขียนโค้ดในทีมของเรา คนเกือบทั้งหมดรู้วิธีการเขียนโปรแกรมและเขียนการทดสอบอัตโนมัติ โหลดสคริปต์ และสคริปต์อัตโนมัติ เราให้ความใส่ใจเป็นพิเศษกับสิ่งนี้ เนื่องจากแม้แต่การสนับสนุนผลิตภัณฑ์ก็ยังต้องการระบบอัตโนมัติในระดับสูง เราพยายามให้คำแนะนำและช่วยเหลือเพื่อนร่วมงานที่ไม่เคยเขียนโปรแกรมมาก่อนเสมอ และมอบหมายงานเล็กๆ น้อยๆ ให้พวกเขาดำเนินการ

เนื่องจากการแพร่ระบาดของไวรัสโคโรนา เราจึงย้ายทั้งทีมไปทำงานจากระยะไกล ความพร้อมใช้งานของเครื่องมือทั้งหมดสำหรับการจัดการการพัฒนา เวิร์กโฟลว์ที่สร้างขึ้นใน Jira และ GitLab ทำให้สามารถผ่านขั้นตอนนี้ได้อย่างง่ายดาย เวลาหลายเดือนที่ต้องอยู่ห่างไกลแสดงให้เห็นว่าประสิทธิภาพการทำงานของทีมไม่ได้ลดลง สำหรับหลายๆ คน ความสะดวกสบายในการทำงานเพิ่มขึ้น สิ่งเดียวที่ขาดหายไปคือการสื่อสารสด

การประชุมทีมระยะไกล

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

การประชุมระหว่างการทำงานทางไกล

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

กองเทคโนโลยีของการแก้ปัญหา

พื้นที่เก็บข้อมูลมาตรฐานและเครื่องมือ CI/CD สำหรับ X5 คือ GitLab เราใช้มันสำหรับการจัดเก็บโค้ด การทดสอบอย่างต่อเนื่อง และการปรับใช้เพื่อทดสอบและเซิร์ฟเวอร์ที่ใช้งานจริง นอกจากนี้เรายังใช้หลักปฏิบัติในการตรวจสอบโค้ด เมื่อเพื่อนร่วมงานอย่างน้อย 2 คนต้องอนุมัติการเปลี่ยนแปลงที่ทำโดยนักพัฒนาในโค้ด เครื่องวิเคราะห์โค้ดแบบคงที่ SonarQube และ JaCoCo ช่วยให้โค้ดของเราสะอาดและรับประกันความครอบคลุมการทดสอบหน่วยในระดับที่ต้องการ การเปลี่ยนแปลงรหัสทั้งหมดจะต้องผ่านการตรวจสอบเหล่านี้ สคริปต์ทดสอบทั้งหมดที่รันด้วยตนเองจะเป็นแบบอัตโนมัติในภายหลัง

เพื่อให้การนำกระบวนการทางธุรกิจไปใช้อย่างประสบความสำเร็จโดย “Marcus” เราต้องแก้ไขปัญหาทางเทคโนโลยีหลายประการ ตามลำดับ

ภารกิจที่ 1. ความจำเป็นในการปรับขยายแนวนอนของระบบ

เพื่อแก้ไขปัญหานี้ เราเลือกแนวทางไมโครเซอร์วิสสำหรับสถาปัตยกรรม ในเวลาเดียวกัน การเข้าใจขอบเขตความรับผิดชอบของบริการเป็นสิ่งสำคัญมาก เราพยายามแบ่งพวกมันออกเป็นการดำเนินธุรกิจโดยคำนึงถึงลักษณะเฉพาะของกระบวนการ ตัวอย่างเช่นการยอมรับที่คลังสินค้าไม่ใช่เรื่องบ่อยนัก แต่เป็นการดำเนินการขนาดใหญ่มากในระหว่างนี้จำเป็นต้องได้รับข้อมูลจากหน่วยงานกำกับดูแลของรัฐอย่างรวดเร็วเกี่ยวกับหน่วยของสินค้าที่ได้รับการยอมรับซึ่งจำนวนในการจัดส่งครั้งเดียวถึง 600000 ตรวจสอบการรับสินค้าเข้าคลังสินค้าและส่งคืนข้อมูลที่จำเป็นทั้งหมดสำหรับระบบคลังสินค้าอัตโนมัติ แต่การจัดส่งจากคลังสินค้ามีความเข้มข้นมากกว่ามาก แต่ในขณะเดียวกันก็ดำเนินการโดยใช้ข้อมูลจำนวนน้อย

เราใช้บริการทั้งหมดแบบไร้สัญชาติ และพยายามแบ่งการดำเนินงานภายในออกเป็นขั้นตอนต่างๆ โดยใช้สิ่งที่เราเรียกว่าหัวข้อตนเองของ Kafka นี่คือเวลาที่ไมโครเซอร์วิสส่งข้อความถึงตัวเอง ซึ่งช่วยให้คุณสร้างสมดุลระหว่างโหลดในการดำเนินการที่ใช้ทรัพยากรมากขึ้น และลดความยุ่งยากในการบำรุงรักษาผลิตภัณฑ์ แต่จะเพิ่มเติมในภายหลัง

เราตัดสินใจแยกโมดูลสำหรับการโต้ตอบกับระบบภายนอกออกเป็นบริการแยกกัน ทำให้สามารถแก้ไขปัญหาการเปลี่ยนแปลง API ของระบบภายนอกบ่อยครั้ง โดยแทบไม่มีผลกระทบต่อบริการที่มีฟังก์ชันทางธุรกิจ

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

ไมโครเซอร์วิสทั้งหมดถูกปรับใช้ในคลัสเตอร์ OpenShift ซึ่งแก้ปัญหาทั้งปัญหาการปรับขนาดไมโครเซอร์วิสแต่ละรายการ และทำให้เราไม่สามารถใช้เครื่องมือค้นหาบริการของบริษัทอื่นได้

ภารกิจที่ 2 ความจำเป็นในการรักษาโหลดสูงและการแลกเปลี่ยนข้อมูลที่เข้มข้นมากระหว่างบริการแพลตฟอร์ม: ในช่วงเปิดตัวโครงการเพียงอย่างเดียว จะมีการดำเนินการประมาณ 600 รายการต่อวินาที เราคาดว่ามูลค่านี้จะเพิ่มขึ้นเป็น 5000 ปฏิบัติการ/วินาที เนื่องจากร้านค้าปลีกเชื่อมต่อกับแพลตฟอร์มของเรา

ปัญหานี้แก้ไขได้ด้วยการปรับใช้คลัสเตอร์ Kafka และละทิ้งการโต้ตอบแบบซิงโครนัสระหว่างไมโครเซอร์วิสของแพลตฟอร์มเกือบทั้งหมด ซึ่งจำเป็นต้องมีการวิเคราะห์ความต้องการของระบบอย่างระมัดระวัง เนื่องจากการดำเนินการทั้งหมดไม่สามารถเป็นแบบอะซิงโครนัสได้ ในเวลาเดียวกัน เราไม่เพียงแต่ส่งกิจกรรมผ่านนายหน้าเท่านั้น แต่ยังส่งข้อมูลทางธุรกิจที่จำเป็นทั้งหมดในข้อความด้วย ดังนั้นขนาดข้อความจึงสูงถึงหลายร้อยกิโลไบต์ ขีดจำกัดขนาดข้อความใน Kafka กำหนดให้เราต้องคาดการณ์ขนาดข้อความอย่างแม่นยำ และหากจำเป็น เราจะแบ่งขนาดข้อความเหล่านั้น แต่การแบ่งส่วนนั้นสมเหตุสมผลและเกี่ยวข้องกับการดำเนินธุรกิจ
เช่น เราแบ่งสินค้าที่มาถึงรถออกเป็นกล่องๆ สำหรับการดำเนินการแบบซิงโครนัส ไมโครเซอร์วิสแยกต่างหากจะถูกจัดสรรและดำเนินการทดสอบโหลดอย่างละเอียด การใช้ Kafka นำเสนอความท้าทายอีกประการหนึ่งให้กับเรา - การทดสอบการทำงานของบริการของเราโดยคำนึงถึงการบูรณาการของ Kafka ทำให้การทดสอบหน่วยทั้งหมดของเราไม่พร้อมกัน เราแก้ไขปัญหานี้ด้วยการเขียนวิธีการอรรถประโยชน์ของเราเองโดยใช้ Embedded Kafka Broker สิ่งนี้ไม่ได้ขจัดความจำเป็นในการเขียนการทดสอบหน่วยสำหรับแต่ละวิธี แต่เราต้องการทดสอบกรณีที่ซับซ้อนโดยใช้ Kafka

มีการให้ความสนใจอย่างมากกับบันทึกการติดตามเพื่อไม่ให้ TraceId สูญหายเมื่อมีข้อยกเว้นเกิดขึ้นระหว่างการให้บริการหรือเมื่อทำงานกับชุด Kafka และหากไม่มีปัญหาพิเศษกับปัญหาแรก ในกรณีที่สอง เราจะถูกบังคับให้บันทึก TraceIds ทั้งหมดที่มาพร้อมแบตช์ และเลือกหนึ่งรายการเพื่อติดตามต่อ จากนั้น เมื่อค้นหาด้วย TraceId ดั้งเดิม ผู้ใช้จะค้นหาได้อย่างง่ายดายว่าการติดตามดำเนินต่อไปที่ใด

ภารกิจที่ 3 ความจำเป็นในการจัดเก็บข้อมูลจำนวนมาก: X1 มีฉลากยาสูบเพียงอย่างเดียวมากกว่า 5 พันล้านฉลากต่อปี พวกเขาต้องการการเข้าถึงอย่างต่อเนื่องและรวดเร็ว โดยรวมแล้ว ระบบจะต้องประมวลผลบันทึกประวัติการเคลื่อนไหวของสินค้าที่มีฉลากเหล่านี้ประมาณ 10 หมื่นล้านรายการ

เพื่อแก้ไขปัญหาที่สาม จึงมีการเลือกฐานข้อมูล NoSQL MongoDB เราได้สร้างส่วนแบ่งข้อมูลจำนวน 5 โหนด และแต่ละโหนดมีชุดจำลองของเซิร์ฟเวอร์ 3 เครื่อง ซึ่งช่วยให้คุณสามารถปรับขนาดระบบในแนวนอน เพิ่มเซิร์ฟเวอร์ใหม่ให้กับคลัสเตอร์ และรับประกันความทนทานต่อข้อผิดพลาด ที่นี่เราพบปัญหาอื่น - การรับรองการทำธุรกรรมในคลัสเตอร์ mongo โดยคำนึงถึงการใช้ไมโครเซอร์วิสที่ปรับขนาดได้ในแนวนอน ตัวอย่างเช่น งานอย่างหนึ่งของระบบของเราคือการระบุความพยายามในการจำหน่ายผลิตภัณฑ์ต่อด้วยรหัสการติดฉลากเดียวกัน ที่นี่ ภาพซ้อนทับจะปรากฏขึ้นพร้อมกับการสแกนที่ผิดพลาดหรือการดำเนินการที่ผิดพลาดโดยพนักงานเก็บเงิน เราพบว่าการทำซ้ำดังกล่าวสามารถเกิดขึ้นได้ทั้งภายในชุดงาน Kafka เดียวที่กำลังประมวลผล และภายในสองชุดที่ได้รับการประมวลผลพร้อมกัน ดังนั้นการตรวจสอบรายการซ้ำโดยการสืบค้นฐานข้อมูลจึงไม่ได้ให้ผลอะไรเลย สำหรับไมโครเซอร์วิสแต่ละรายการ เราได้แก้ไขปัญหาแยกกันตามตรรกะทางธุรกิจของบริการนี้ ตัวอย่างเช่น สำหรับเช็ค เราได้เพิ่มเช็คภายในแบทช์และการประมวลผลแยกต่างหากสำหรับลักษณะที่ซ้ำกันเมื่อแทรก

เพื่อให้แน่ใจว่าการทำงานของผู้ใช้กับประวัติการดำเนินงานจะไม่ส่งผลกระทบในทางใดทางหนึ่งต่อสิ่งที่สำคัญที่สุด - การทำงานของกระบวนการทางธุรกิจของเรา เราได้แยกข้อมูลประวัติทั้งหมดออกเป็นบริการแยกต่างหากพร้อมฐานข้อมูลแยกต่างหาก ซึ่งรับข้อมูลผ่าน Kafka ด้วย . ด้วยวิธีนี้ ผู้ใช้จะทำงานกับบริการแบบแยกส่วนได้โดยไม่ส่งผลกระทบต่อบริการที่ประมวลผลข้อมูลสำหรับการดำเนินงานที่กำลังดำเนินอยู่

งานที่ 4: การประมวลผลคิวใหม่และการตรวจสอบ:

ในระบบแบบกระจาย ปัญหาและข้อผิดพลาดเกิดขึ้นอย่างหลีกเลี่ยงไม่ได้ในความพร้อมใช้งานของฐานข้อมูล คิว และแหล่งข้อมูลภายนอก ในกรณีของ Marcus สาเหตุของข้อผิดพลาดดังกล่าวคือการบูรณาการเข้ากับระบบภายนอก จำเป็นต้องค้นหาวิธีแก้ปัญหาที่จะอนุญาตให้มีการร้องขอซ้ำสำหรับการตอบกลับที่ผิดพลาดโดยมีการหมดเวลาตามที่ระบุ แต่ในขณะเดียวกันก็ไม่หยุดประมวลผลคำขอที่สำเร็จในคิวหลัก เพื่อจุดประสงค์นี้ จึงมีการเลือกแนวคิดที่เรียกว่า "การลองใหม่ตามหัวข้อ" สำหรับแต่ละหัวข้อหลัก จะมีการสร้างหัวข้อลองใหม่ตั้งแต่หนึ่งหัวข้อขึ้นไปเพื่อส่งข้อความที่ผิดพลาด และในขณะเดียวกันก็ขจัดความล่าช้าในการประมวลผลข้อความจากหัวข้อหลัก รูปแบบปฏิสัมพันธ์ -

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

เพื่อนำโครงร่างดังกล่าวไปใช้ เราต้องการสิ่งต่อไปนี้: เพื่อรวมโซลูชันนี้เข้ากับ Spring และหลีกเลี่ยงการทำซ้ำโค้ด ขณะท่องเว็บ เราพบวิธีแก้ปัญหาที่คล้ายกันซึ่งใช้ Spring BeanPostProccessor แต่ดูเหมือนจะยุ่งยากโดยไม่จำเป็นสำหรับเรา ทีมงานของเราได้สร้างโซลูชันที่เรียบง่ายขึ้นซึ่งช่วยให้เราสามารถรวมเข้ากับวัฏจักรฤดูใบไม้ผลิเพื่อสร้างผู้บริโภคและเพิ่ม Retry Consumers เพิ่มเติมได้ เราเสนอต้นแบบโซลูชันของเราให้กับทีม Spring คุณสามารถดูได้ ที่นี่. จำนวน Retry Consumers และจำนวนความพยายามสำหรับผู้ใช้บริการแต่ละรายได้รับการกำหนดค่าผ่านพารามิเตอร์ ขึ้นอยู่กับความต้องการของกระบวนการทางธุรกิจ และเพื่อให้ทุกอย่างทำงานได้ สิ่งที่เหลืออยู่คือการเพิ่มคำอธิบายประกอบ org.springframework.kafka.annotation.KafkaListener ซึ่งนักพัฒนา Spring ทุกคนคุ้นเคย

หากข้อความไม่สามารถประมวลผลได้หลังจากลองใหม่ทั้งหมด ข้อความจะไปที่ DLT (หัวข้อจดหมายที่ส่งถึงไม่ได้) โดยใช้ Spring DeadLetterPublishingRecoverer ตามคำร้องขอการสนับสนุน เราได้ขยายฟังก์ชันการทำงานนี้และสร้างบริการแยกต่างหากที่ช่วยให้คุณสามารถดูข้อความที่รวมอยู่ใน DLT, stackTrace, TraceId และข้อมูลที่เป็นประโยชน์อื่นๆ เกี่ยวกับข้อความเหล่านั้น นอกจากนี้ ยังมีการเพิ่มการตรวจสอบและการแจ้งเตือนในหัวข้อ DLT ทั้งหมด และตอนนี้ การปรากฏตัวของข้อความในหัวข้อ DLT ก็เป็นเหตุผลในการวิเคราะห์และแก้ไขข้อบกพร่อง สะดวกมาก - ด้วยชื่อของหัวข้อเราจะเข้าใจทันทีว่าปัญหาเกิดขึ้นขั้นตอนใดของกระบวนการซึ่งทำให้การค้นหาสาเหตุของปัญหาเร็วขึ้นอย่างมาก

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

ล่าสุด เราได้ใช้อินเทอร์เฟซที่ช่วยให้เราสามารถส่งข้อความอีกครั้งโดยใช้การสนับสนุนของเราหลังจากกำจัดสาเหตุแล้ว (เช่น การกู้คืนฟังก์ชันการทำงานของระบบภายนอก) และแน่นอน สร้างข้อบกพร่องที่เกี่ยวข้องสำหรับการวิเคราะห์ นี่คือจุดที่หัวข้อส่วนตัวของเรามีประโยชน์: เพื่อไม่ให้รีสตาร์ทห่วงโซ่การประมวลผลที่ยาวนาน คุณสามารถรีสตาร์ทจากขั้นตอนที่ต้องการได้

“ เดินในรองเท้าของฉัน” - เดี๋ยวนะ มีการทำเครื่องหมายไว้หรือเปล่า?

การทำงานของแพลตฟอร์ม

แพลตฟอร์มดังกล่าวมีการดำเนินงานอย่างมีประสิทธิผลแล้ว ทุกๆ วันเราดำเนินการจัดส่งและจัดส่ง เชื่อมต่อศูนย์กระจายสินค้าและร้านค้าใหม่ทุกวัน ในฐานะส่วนหนึ่งของโครงการนำร่อง ระบบจะทำงานร่วมกับกลุ่มผลิตภัณฑ์ "ยาสูบ" และ "รองเท้า"

ทีมงานทั้งหมดของเรามีส่วนร่วมในการดำเนินการนำร่อง วิเคราะห์ปัญหาที่เกิดขึ้น และให้คำแนะนำในการปรับปรุงผลิตภัณฑ์ของเรา ตั้งแต่การปรับปรุงบันทึกไปจนถึงกระบวนการที่เปลี่ยนแปลง

เพื่อไม่ให้เกิดข้อผิดพลาดซ้ำ ทุกกรณีที่พบในระหว่างการทดลองนำร่องจะแสดงในการทดสอบอัตโนมัติ การมีการทดสอบอัตโนมัติและการทดสอบหน่วยจำนวนมากทำให้คุณสามารถดำเนินการทดสอบการถดถอยและติดตั้งโปรแกรมแก้ไขด่วนได้อย่างแท้จริงภายในไม่กี่ชั่วโมง

ตอนนี้เราพัฒนาและปรับปรุงแพลตฟอร์มของเราอย่างต่อเนื่อง และเผชิญกับความท้าทายใหม่ ๆ อย่างต่อเนื่อง หากคุณสนใจ เราจะพูดถึงโซลูชันของเราในบทความต่อไปนี้

ที่มา: will.com

เพิ่มความคิดเห็น