การพัฒนาโครงการที่มีภาระงานสูงในภาษาใดๆ ต้องใช้แนวทางพิเศษและการใช้เครื่องมือพิเศษ แต่เมื่อพูดถึงแอปพลิเคชันใน PHP สถานการณ์อาจรุนแรงขึ้นจนคุณต้องพัฒนา เช่น
ฮีโร่ของโอกาสนี้คือแอปพลิเคชัน 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' ]
... และร่างมันออกมา แผนภูมิหางเสือ. สิ่งที่น่าสนใจคือมีเพียงตัวสร้างการกำหนดค่าตามจำนวนเรพลิกาเท่านั้น (ถ้าใครมีตัวเลือกที่กระชับและหรูหรากว่านี้แบ่งปันในความคิดเห็น):
{{- $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 }}
}
}
}
}
เราเปิดตัวในสภาพแวดล้อมการทดสอบและตรวจสอบ:
# 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 >
การค้นหาข้อความข้อผิดพลาดไม่ได้ให้ผลลัพธ์ใด ๆ แต่สำหรับข้อความค้นหา “
NB: โปรโตคอล ASCII ใน memcached ช้ากว่าไบนารี่ และวิธีการมาตรฐานของการแฮชคีย์ที่สอดคล้องกันจะใช้ได้กับโปรโตคอลไบนารี่เท่านั้น แต่สิ่งนี้ไม่ได้สร้างปัญหาให้กับบางกรณีโดยเฉพาะ
เคล็ดลับอยู่ในกระเป๋า: สิ่งที่คุณต้องทำคือเปลี่ยนไปใช้โปรโตคอล ASCII แล้วทุกอย่างจะสำเร็จ.... แต่ในกรณีนี้นิสัยชอบหาคำตอบค่ะ
ใช่ ชื่อตัวเลือกที่ถูกต้องคือ memcached.sess_binary_protocol
. จะต้องปิดการใช้งาน หลังจากนั้นเซสชันจะเริ่มทำงาน สิ่งที่เหลืออยู่คือการใส่คอนเทนเนอร์ที่มี mcrouter ลงในพ็อดด้วย PHP!
ข้อสรุป
ดังนั้น ด้วยการเปลี่ยนแปลงโครงสร้างพื้นฐานเพียงอย่างเดียว เราจึงสามารถแก้ไขปัญหาได้: ปัญหาเกี่ยวกับความทนทานต่อข้อผิดพลาดของ memcached ได้รับการแก้ไขแล้ว และความน่าเชื่อถือของพื้นที่จัดเก็บแคชก็เพิ่มขึ้น นอกเหนือจากข้อได้เปรียบที่ชัดเจนสำหรับแอปพลิเคชันแล้ว สิ่งนี้ยังให้พื้นที่สำหรับการซ้อมรบเมื่อทำงานบนแพลตฟอร์ม: เมื่อส่วนประกอบทั้งหมดมีเงินสำรอง ชีวิตของผู้ดูแลระบบก็จะง่ายขึ้นอย่างมาก ใช่ วิธีการนี้ก็มีข้อเสียเหมือนกัน อาจดูเหมือนเป็น "ไม้ค้ำยัน" แต่ถ้าช่วยประหยัดเงิน ฝังปัญหา และไม่ก่อให้เกิดปัญหาใหม่ ทำไมจะไม่ได้ล่ะ?
PS
อ่านเพิ่มเติมในบล็อกของเรา:
- "ฝึกฝนกับ dapp" (ใช้ symfony-demo เป็นตัวอย่าง):
ส่วนที่ 1 (การสร้างแอปพลิเคชันอย่างง่าย) иส่วนที่ 2 (ปรับใช้อิมเมจ Docker กับ Kubernetes โดยใช้ Helm) ; - «
จากชีวิตจริงกับ Kubernetes: วิธีที่เซิร์ฟเวอร์ HTTP ไม่สนับสนุนชาวสเปน '
ที่มา: will.com