使用任何語言開發高負載專案都需要特殊的方法和使用特殊的工具,但是當涉及到 PHP 中的應用程式時,情況可能會變得如此嚴重,以至於您必須開發,例如,
這次的主角是基於 symfony 2.3 框架的 PHP 應用程序,該應用程式根本沒有包含在更新的業務計劃中。 除了相當標準的會話儲存之外,該項目還充分利用了 「緩存一切」政策 在 memcached 中:回應對資料庫和 API 伺服器的請求、各種標誌、用於同步程式碼執行的鎖定等等。 在這種情況下,memcached 的故障對於應用程式的運作來說是致命的。 此外,快取遺失會導致嚴重後果:DBMS 開始爆裂、API 服務開始禁止請求等。 穩定局勢可能需要數十分鐘,在此期間服務將非常慢或完全不可用。
我們需要提供 輕鬆水平擴展應用程式的能力, IE。 對原始程式碼進行最少的更改並保留全部功能。 使快取不僅能夠抵抗故障,而且還要盡量減少由此造成的資料遺失。
memcached 本身有什麼問題?
一般來說,PHP 的 memcached 擴充功能支援開箱即用的分散式資料和會話儲存。 一致的金鑰雜湊機制可讓您將資料均勻地放置在許多伺服器上,將每個特定金鑰唯一地定址到群組中的特定伺服器,並且內建的故障轉移工具確保了快取服務的高可用性(但不幸的是, 沒有數據).
會話儲存的情況要好一些:您可以配置 memcached.sess_number_of_replicas
,這樣一來,資料將同時儲存在多台伺服器上,並且當其中一個 memcached 實例出現故障時,資料將從其他伺服器轉移。 但是,如果伺服器在沒有資料的情況下重新上線(通常在重新啟動後發生),則某些金鑰將按照有利於伺服器的方式重新分配。 事實上這意味著 會話資料遺失,因為如果發生遺失,就無法「轉到」另一個副本。
標準庫工具主要針對 水平的 縮放:它們允許您將快取增加到巨大的大小,並提供從不同伺服器上託管的程式碼對其的存取。 不過,在我們的情況下,儲存的資料量不超過幾GB,一兩個節點的效能就已經足夠了。 因此,唯一有用的標準工具可能是確保 memcached 的可用性,同時維持至少一個快取執行個體處於工作狀態。 然而,甚至不可能利用這個機會......這裡值得回顧一下專案中使用的框架的古老性,這就是為什麼不可能讓應用程式與伺服器集區一起工作的原因。 我們也不要忘記會話資料的遺失:客戶的眼睛因使用者大量的註銷而抽搐。
理想情況下是必要的 複製 memcached 中的記錄並繞過副本 如果出現錯誤或錯誤。 幫助我們實施這項策略
微路由器
這是Facebook為了解決其問題而開發的memcached路由器。 它支援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 的有用鏈接
建置並運行微處理器
我們的應用程式(以及 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:memcached 中的 ASCII 協定比二進位協定慢,且一致性金鑰雜湊的標準方法僅適用於二進位協定。 但這不會對特定情況造成問題。
訣竅就在眼前:您所要做的就是切換到 ASCII 協議,一切都會正常工作... 然而,在這種情況下,尋找答案的習慣
是的,正確的選項名稱是 memcached.sess_binary_protocol
。 必須將其停用,之後會話才會開始工作。 剩下的就是將帶有 mcrouter 的容器放入帶有 PHP 的 pod 中!
結論
因此,透過基礎設施的改變,我們就能夠解決這個問題:memcached的容錯問題已經解決,並且快取儲存的可靠性也提高了。 除了為應用程式帶來明顯的優勢之外,這還為在平台上工作時提供了迴旋餘地:當所有組件都有儲備時,管理員的工作就大大簡化了。 是的,這種方法也有其缺點,它可能看起來像“拐杖”,但如果它省錢,埋葬問題並且不會引起新的問題 - 為什麼不呢?
聚苯乙烯
另請閱讀我們的博客:
- “使用 dapp 進行練習” (以 symfony-demo 為例):
第 1 部分(建立簡單的應用程式) и第 2 部分(使用 Helm 將 Docker 映像部署到 Kubernetes) ; - «
來自 Kubernetes 的生活:HTTP 伺服器為何不利於西班牙人 “。
來源: www.habr.com