การใช้ mcrouter เพื่อปรับขนาด memcached ในแนวนอน

การใช้ mcrouter เพื่อปรับขนาด memcached ในแนวนอน

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

ฮีโร่ของโอกาสนี้คือแอปพลิเคชัน PHP ที่ใช้เฟรมเวิร์ก Symfony 2.3 ซึ่งไม่รวมอยู่ในแผนธุรกิจที่จะอัปเดตเลย นอกเหนือจากพื้นที่จัดเก็บเซสชั่นมาตรฐานแล้ว โปรเจ็กต์นี้ยังใช้ประโยชน์อย่างเต็มที่จาก นโยบาย "แคชทุกอย่าง" ใน memcached: การตอบสนองต่อคำขอไปยังฐานข้อมูลและเซิร์ฟเวอร์ API แฟล็กต่างๆ การล็อคสำหรับการซิงโครไนซ์การเรียกใช้โค้ด และอื่นๆ อีกมากมาย ในสถานการณ์เช่นนี้ การสลายตัวของ memcached อาจส่งผลร้ายแรงต่อการทำงานของแอปพลิเคชัน นอกจากนี้ การสูญเสียแคชยังนำไปสู่ผลกระทบร้ายแรง: DBMS เริ่มระเบิด บริการ API เริ่มแบนคำขอ ฯลฯ การรักษาเสถียรภาพของสถานการณ์อาจใช้เวลาหลายสิบนาที และในช่วงเวลานี้ บริการจะช้ามากหรือไม่สามารถใช้งานได้เลย

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

เกิดอะไรขึ้นกับ memcached เอง?

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

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

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

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

ไมโครเตอร์

นี่คือเราเตอร์ memcached ที่พัฒนาโดย Facebook เพื่อแก้ไขปัญหา รองรับโปรโตคอลข้อความ memcached ซึ่งอนุญาต การติดตั้ง memcached ขนาด ถึงสัดส่วนที่บ้าคลั่ง คำอธิบายโดยละเอียดของ mcrouter สามารถพบได้ใน ประกาศนี้. เหนือสิ่งอื่นใด ฟังก์ชั่นที่กว้างขวาง มันสามารถทำสิ่งที่เราต้องการได้:

  • ทำซ้ำบันทึก;
  • สำรองไปยังเซิร์ฟเวอร์อื่นในกลุ่มหากเกิดข้อผิดพลาด

ลงมือทำธุรกิจ!

การกำหนดค่าไมโครเตอร์

ฉันจะตรงไปที่การกำหนดค่า:

{
 "pools": {
   "pool00": {
     "servers": [
       "mc-0.mc:11211",
       "mc-1.mc:11211",
       "mc-2.mc:11211"
   },
   "pool01": {
     "servers": [
       "mc-1.mc:11211",
       "mc-2.mc:11211",
       "mc-0.mc:11211"
   },
   "pool02": {
     "servers": [
       "mc-2.mc:11211",
       "mc-0.mc:11211",
       "mc-1.mc:11211"
 },
 "route": {
   "type": "OperationSelectorRoute",
   "default_policy": "AllMajorityRoute|Pool|pool00",
   "operation_policies": {
     "get": {
       "type": "RandomRoute",
       "children": [
         "MissFailoverRoute|Pool|pool02",
         "MissFailoverRoute|Pool|pool00",
         "MissFailoverRoute|Pool|pool01"
       ]
     }
   }
 }
}

ทำไมต้องมีสามสระ? เหตุใดเซิร์ฟเวอร์จึงถูกทำซ้ำ? เรามาดูกันว่ามันทำงานอย่างไร

  • ในการกำหนดค่านี้ mcrouter จะเลือกเส้นทางที่คำขอจะถูกส่งไปตามคำสั่งคำขอ ผู้ชายบอกเรื่องนี้กับเขา OperationSelectorRoute.
  • คำขอ GET ไปที่ตัวจัดการ RandomRouteซึ่งสุ่มเลือกพูลหรือเส้นทางระหว่างวัตถุอาเรย์ children. แต่ละองค์ประกอบของอาร์เรย์นี้จะเป็นตัวจัดการ MissFailoverRouteซึ่งจะผ่านแต่ละเซิร์ฟเวอร์ในพูลจนกว่าจะได้รับการตอบกลับพร้อมข้อมูลซึ่งจะถูกส่งกลับไปยังไคลเอนต์
  • หากเราใช้แต่เพียงผู้เดียว MissFailoverRoute ด้วยพูลเซิร์ฟเวอร์สามเซิร์ฟเวอร์ คำขอทั้งหมดจะมาก่อนอินสแตนซ์ memcached แรก และส่วนที่เหลือจะได้รับคำขอตามปริมาณคงเหลือเมื่อไม่มีข้อมูล แนวทางดังกล่าวก็จะนำไปสู่ โหลดมากเกินไปบนเซิร์ฟเวอร์แรกในรายการดังนั้นจึงตัดสินใจสร้างพูลสามพูลที่มีที่อยู่ในลำดับต่างกันและเลือกแบบสุ่ม
  • คำขออื่นๆ ทั้งหมด (และนี่คือบันทึก) ได้รับการประมวลผลโดยใช้ AllMajorityRoute. ตัวจัดการนี้ส่งคำขอไปยังเซิร์ฟเวอร์ทั้งหมดในพูลและรอการตอบกลับจากอย่างน้อย N/2 + 1 ในนั้น จากการใช้งาน AllSyncRoute สำหรับการดำเนินการเขียนจะต้องละทิ้ง เนื่องจากวิธีนี้ต้องการการตอบสนองเชิงบวกจาก ทั้งหมด เซิร์ฟเวอร์ในกลุ่ม - มิฉะนั้นจะกลับมา SERVER_ERROR. แม้ว่า mcrouter จะเพิ่มข้อมูลลงในแคชที่มีอยู่ แต่การเรียกใช้ฟังก์ชัน PHP จะส่งกลับข้อผิดพลาด และจะแจ้งให้ทราบ AllMajorityRoute ไม่เข้มงวดมากนักและอนุญาตให้นำหน่วยออกไปให้บริการได้ไม่เกินครึ่งหนึ่งโดยไม่มีปัญหาที่อธิบายไว้ข้างต้น

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

NB: คุณอาจพบลิงค์ที่เป็นประโยชน์สำหรับการเรียนรู้ mcrouter เอกสารประกอบในวิกิ и ปัญหาโครงการ (รวมถึงที่ปิด) ซึ่งเป็นตัวแทนของคลังเก็บของที่มีการกำหนดค่าต่างๆ

การสร้างและใช้งาน mcrouter

แอปพลิเคชันของเรา (และ memcached เอง) ทำงานใน Kubernetes ดังนั้นจึงมี mcrouter อยู่ที่นั่นด้วย สำหรับ ประกอบภาชนะ เราใช้ เวรซึ่งการกำหนดค่าจะมีลักษณะดังนี้:

NB: รายการที่ระบุในบทความได้รับการเผยแพร่ในพื้นที่เก็บข้อมูล แฟลต/แมคโครเตอร์.

configVersion: 1
project: mcrouter
deploy:
 namespace: '[[ env ]]'
 helmRelease: '[[ project ]]-[[ env ]]'
---
image: mcrouter
from: ubuntu:16.04
mount:
- from: tmp_dir
 to: /var/lib/apt/lists
- from: build_dir
 to: /var/cache/apt
ansible:
 beforeInstall:
 - name: Install prerequisites
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: Add mcrouter APT key
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: Add mcrouter Repo
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: Set timezone
   timezone:
     name: "Europe/Moscow"
 - name: Ensure a locale exists
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Install mcrouter
   apt:
     name: [ 'mcrouter' ]

(เวิร์ฟ.yaml)

... และร่างมันออกมา แผนภูมิหางเสือ. สิ่งที่น่าสนใจคือมีเพียงตัวสร้างการกำหนดค่าตามจำนวนเรพลิกาเท่านั้น (ถ้าใครมีตัวเลือกที่กระชับและหรูหรากว่านี้แบ่งปันในความคิดเห็น):

{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Заполняем  массив двумя копиями серверов: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
   {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{- /* Смещаясь по массиву, получаем N срезов: "[0 1 2] [1 2 0] [2 0 1]" */ -}}
{{- range $i, $_ := until $count -}}
 {{- $pool := dict "servers" (slice $servers $i (add $i $count)) -}}
 {{- $_ := set $pools (printf "MissFailoverRoute|Pool|pool%02d" $i) $pool -}}
{{- end -}}
---
apiVersion: v1
kind: ConfigMap
metadata:
 name: mcrouter
data:
 config.json: |
   {
     "pools": {{- $pools | toJson | replace "MissFailoverRoute|Pool|" "" -}},
     "route": {
       "type": "OperationSelectorRoute",
       "default_policy": "AllMajorityRoute|Pool|pool00",
       "operation_policies": {
         "get": {
           "type": "RandomRoute",
           "children": {{- keys $pools | toJson }}
         }
       }
     }
   }

(10-mcrouter.yaml)

เราเปิดตัวในสภาพแวดล้อมการทดสอบและตรวจสอบ:

# php -a
Interactive mode enabled

php > # Проверяем запись и чтение
php > $m = new Memcached();
php > $m->addServer('mcrouter', 11211);
php > var_dump($m->set('test', 'value'));
bool(true)
php > var_dump($m->get('test'));
string(5) "value"
php > # Работает! Тестируем работу сессий:
php > ini_set('session.save_handler', 'memcached');
php > ini_set('session.save_path', 'mcrouter:11211');
php > var_dump(session_start());
PHP Warning:  Uncaught Error: Failed to create session ID: memcached (path: mcrouter:11211) in php shell code:1
Stack trace:
#0 php shell code(1): session_start()
#1 {main}
  thrown in php shell code on line 1
php > # Не заводится… Попробуем задать session_id:
php > session_id("zzz");
php > var_dump(session_start());
PHP Warning:  session_start(): Cannot send session cookie - headers already sent by (output started at php shell code:1) in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Unable to clear session lock record in php shell code on line 1
PHP Warning:  session_start(): Failed to read session data: memcached (path: mcrouter:11211) in php shell code on line 1
bool(false)
php >

การค้นหาข้อความข้อผิดพลาดไม่ได้ให้ผลลัพธ์ใด ๆ แต่สำหรับข้อความค้นหา “mcrouter php.ini“ในแถวหน้าคือปัญหาที่เก่าแก่ที่สุดที่ยังไม่ได้รับการแก้ไขของโครงการ - ขาดการสนับสนุน โปรโตคอลไบนารี memcached

NB: โปรโตคอล ASCII ใน memcached ช้ากว่าไบนารี่ และวิธีการมาตรฐานของการแฮชคีย์ที่สอดคล้องกันจะใช้ได้กับโปรโตคอลไบนารี่เท่านั้น แต่สิ่งนี้ไม่ได้สร้างปัญหาให้กับบางกรณีโดยเฉพาะ

เคล็ดลับอยู่ในกระเป๋า: สิ่งที่คุณต้องทำคือเปลี่ยนไปใช้โปรโตคอล ASCII แล้วทุกอย่างจะสำเร็จ.... แต่ในกรณีนี้นิสัยชอบหาคำตอบค่ะ เอกสารบน php.net เล่นตลกที่โหดร้าย คุณจะไม่พบคำตอบที่ถูกต้องที่นั่น... เว้นแต่ว่าคุณจะเลื่อนไปจนจบตรงส่วนไหน "บันทึกที่ผู้ใช้มีส่วนร่วม" จะซื่อสัตย์และ คำตอบ downvoted อย่างไม่ยุติธรรม.

ใช่ ชื่อตัวเลือกที่ถูกต้องคือ memcached.sess_binary_protocol. จะต้องปิดการใช้งาน หลังจากนั้นเซสชันจะเริ่มทำงาน สิ่งที่เหลืออยู่คือการใส่คอนเทนเนอร์ที่มี mcrouter ลงในพ็อดด้วย PHP!

ข้อสรุป

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

PS

อ่านเพิ่มเติมในบล็อกของเรา:

ที่มา: will.com

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