Sử dụng mcrouter để chia tỷ lệ memcached theo chiều ngang

Sử dụng mcrouter để chia tỷ lệ memcached theo chiều ngang

Việc phát triển các dự án có tải trọng cao bằng bất kỳ ngôn ngữ nào đòi hỏi một cách tiếp cận đặc biệt và sử dụng các công cụ đặc biệt, nhưng khi nói đến các ứng dụng bằng PHP, tình hình có thể trở nên trầm trọng hơn đến mức bạn phải phát triển, chẳng hạn như: máy chủ ứng dụng riêng. Trong ghi chú này, chúng ta sẽ nói về những vấn đề quen thuộc với việc lưu trữ phiên phân tán và bộ nhớ đệm dữ liệu trong memcached cũng như cách chúng tôi giải quyết những vấn đề này trong một dự án “ward”.

Nhân vật chính của dịp này là một ứng dụng PHP dựa trên framework symfony 2.3, ứng dụng này hoàn toàn không nằm trong kế hoạch cập nhật kinh doanh. Ngoài việc lưu trữ phiên khá chuẩn, dự án này còn tận dụng tối đa Chính sách "lưu trữ mọi thứ" trong memcached: phản hồi các yêu cầu tới cơ sở dữ liệu và máy chủ API, nhiều cờ khác nhau, khóa để đồng bộ hóa việc thực thi mã và hơn thế nữa. Trong tình huống như vậy, sự cố của memcached sẽ ảnh hưởng nghiêm trọng đến hoạt động của ứng dụng. Ngoài ra, việc mất bộ đệm còn dẫn đến những hậu quả nghiêm trọng: DBMS bắt đầu bùng nổ ở các đường nối, các dịch vụ API bắt đầu cấm các yêu cầu, v.v. Việc ổn định tình hình có thể mất hàng chục phút và trong thời gian này, dịch vụ sẽ rất chậm hoặc hoàn toàn không khả dụng.

Chúng tôi cần cung cấp khả năng mở rộng quy mô ứng dụng theo chiều ngang với ít nỗ lực, I E. với những thay đổi tối thiểu đối với mã nguồn và giữ nguyên đầy đủ chức năng. Làm cho bộ đệm không chỉ có khả năng chống lại lỗi mà còn cố gắng giảm thiểu việc mất dữ liệu từ nó.

Có vấn đề gì với memcached vậy?

Nói chung, tiện ích mở rộng memcached cho PHP hỗ trợ dữ liệu phân tán và lưu trữ phiên ngay lập tức. Cơ chế băm khóa nhất quán cho phép bạn đặt dữ liệu đồng đều trên nhiều máy chủ, đánh địa chỉ duy nhất từng khóa cụ thể cho một máy chủ cụ thể trong nhóm và các công cụ chuyển đổi dự phòng tích hợp đảm bảo tính sẵn sàng cao của dịch vụ bộ nhớ đệm (tuy nhiên, thật không may, không có dữ liệu).

Mọi thứ tốt hơn một chút với tính năng lưu trữ phiên: bạn có thể định cấu hình memcached.sess_number_of_replicas, do đó dữ liệu sẽ được lưu trữ trên một số máy chủ cùng một lúc và trong trường hợp một phiên bản memcached bị lỗi, dữ liệu sẽ được truyền từ những máy chủ khác. Tuy nhiên, nếu máy chủ trực tuyến trở lại mà không có dữ liệu (điều thường xảy ra sau khi khởi động lại), một số khóa sẽ được phân phối lại theo hướng có lợi cho máy chủ. Trên thực tế điều này sẽ có nghĩa mất dữ liệu phiên, vì không có cách nào để “chuyển” sang bản sao khác trong trường hợp bị trượt.

Các công cụ thư viện tiêu chuẩn chủ yếu nhằm vào nằm ngang chia tỷ lệ: chúng cho phép bạn tăng bộ đệm lên kích thước khổng lồ và cung cấp quyền truy cập vào bộ đệm từ mã được lưu trữ trên các máy chủ khác nhau. Tuy nhiên, trong trường hợp của chúng tôi, khối lượng dữ liệu được lưu trữ không vượt quá vài gigabyte và hiệu suất của một hoặc hai nút là khá đủ. Theo đó, các công cụ tiêu chuẩn hữu ích duy nhất có thể là đảm bảo tính khả dụng của memcached trong khi duy trì ít nhất một phiên bản bộ đệm trong điều kiện hoạt động. Tuy nhiên, thậm chí không thể tận dụng cơ hội này... Ở đây cần nhớ lại sự cổ xưa của khung được sử dụng trong dự án, đó là lý do tại sao không thể làm cho ứng dụng hoạt động với một nhóm máy chủ. Chúng ta cũng đừng quên việc mất dữ liệu phiên: mắt khách hàng co giật trước lượng lớn người dùng đăng xuất.

Lý tưởng nhất là nó được yêu cầu sao chép các bản ghi trong memcached và bỏ qua các bản sao trong trường hợp có sai sót hoặc nhầm lẫn. Đã giúp chúng tôi thực hiện chiến lược này máy tính.

máy tính

Đây là bộ định tuyến memcached được Facebook phát triển để giải quyết các vấn đề của mình. Nó hỗ trợ giao thức văn bản memcached, cho phép mở rộng quy mô cài đặt memcached đến mức điên rồ. Một mô tả chi tiết về mcrouter có thể được tìm thấy trong thông báo này. Trong số những thứ khác chức năng rộng nó có thể làm những gì chúng ta cần:

  • sao chép hồ sơ;
  • thực hiện dự phòng cho các máy chủ khác trong nhóm nếu xảy ra lỗi.

Tập trung cao độ!

cấu hình microuter

Mình sẽ đi thẳng vào phần config:

{
 "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"
       ]
     }
   }
 }
}

Tại sao ba hồ bơi? Tại sao các máy chủ được lặp lại? Hãy tìm hiểu cách nó hoạt động.

  • Trong cấu hình này, mcrouter chọn đường dẫn mà yêu cầu sẽ được gửi dựa trên lệnh yêu cầu. Chàng trai nói với anh điều này OperationSelectorRoute.
  • NHẬN yêu cầu đi đến trình xử lý RandomRoutechọn ngẫu nhiên một nhóm hoặc tuyến đường giữa các đối tượng mảng children. Mỗi phần tử của mảng này lần lượt là một trình xử lý MissFailoverRoute, nó sẽ đi qua từng máy chủ trong nhóm cho đến khi nhận được phản hồi kèm theo dữ liệu, dữ liệu này sẽ được trả về cho máy khách.
  • Nếu chúng ta sử dụng riêng MissFailoverRoute với một nhóm gồm ba máy chủ, thì tất cả các yêu cầu sẽ đến phiên bản memcached đầu tiên trước tiên và phần còn lại sẽ nhận được các yêu cầu còn sót lại khi không có dữ liệu. Cách tiếp cận như vậy sẽ dẫn đến tải quá mức trên máy chủ đầu tiên trong danh sách, vì vậy người ta quyết định tạo ba nhóm có địa chỉ theo các chuỗi khác nhau và chọn chúng một cách ngẫu nhiên.
  • Tất cả các yêu cầu khác (và đây là bản ghi) được xử lý bằng cách sử dụng AllMajorityRoute. Trình xử lý này gửi yêu cầu đến tất cả các máy chủ trong nhóm và chờ phản hồi từ ít nhất N/2 + 1 trong số đó. Từ việc sử dụng AllSyncRoute đối với hoạt động ghi phải bị loại bỏ, vì phương pháp này yêu cầu phản hồi tích cực từ của tất cả máy chủ trong nhóm - nếu không nó sẽ quay trở lại SERVER_ERROR. Mặc dù mcrouter sẽ thêm dữ liệu vào bộ đệm có sẵn, hàm gọi PHP sẽ trả về một lỗi và sẽ tạo ra thông báo. AllMajorityRoute không quá nghiêm ngặt và cho phép ngừng hoạt động tới một nửa số thiết bị mà không gặp phải các vấn đề được mô tả ở trên.

Những bất lợi chính Sơ đồ này là nếu thực sự không có dữ liệu trong bộ đệm, thì đối với mỗi yêu cầu từ máy khách, N yêu cầu tới memcached sẽ thực sự được thực thi - đến cho tất cả máy chủ trong hồ bơi. Ví dụ: chúng tôi có thể giảm số lượng máy chủ trong nhóm xuống còn hai: hy sinh độ tin cậy của bộ lưu trữ, chúng tôi nhận đượcоtốc độ cao hơn và ít tải hơn từ các yêu cầu đến các phím bị thiếu.

NB: Bạn cũng có thể tìm thấy các liên kết hữu ích để học mcrouter tài liệu trên wiki и vấn đề dự án (bao gồm cả những cái đã đóng), đại diện cho cả một kho chứa nhiều cấu hình khác nhau.

Xây dựng và chạy microter

Ứng dụng của chúng tôi (và chính memcached) chạy trong Kubernetes - theo đó, mcrouter cũng nằm ở đó. Vì lắp ráp container chúng tôi sử dụng người sói, cấu hình sẽ trông như thế này:

NB: Các danh sách đưa ra trong bài viết được xuất bản trong kho flant/mcrouter.

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' ]

(werf.yaml)

... và phác họa nó ra Biểu đồ mũ lái. Điều thú vị là chỉ có một trình tạo cấu hình dựa trên số lượng bản sao (nếu ai có lựa chọn ngắn gọn và trang nhã hơn, hãy chia sẻ trong phần bình luận):

{{- $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)

Chúng tôi triển khai nó vào môi trường thử nghiệm và kiểm tra:

# 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 >

Tìm kiếm văn bản lỗi không trả lại kết quả nào, nhưng tìm kiếm truy vấn “php vi mô"Đi đầu là vấn đề lâu đời nhất chưa được giải quyết của dự án - thiếu hỗ trợ giao thức nhị phân memcached.

NB: Giao thức ASCII trong memcached chậm hơn giao thức nhị phân và phương tiện băm khóa nhất quán tiêu chuẩn chỉ hoạt động với giao thức nhị phân. Nhưng điều này không tạo ra vấn đề cho một trường hợp cụ thể.

Bí quyết nằm trong túi: tất cả những gì bạn phải làm là chuyển sang giao thức ASCII và mọi thứ sẽ hoạt động.... Tuy nhiên, trong trường hợp này, thói quen tìm kiếm câu trả lời trong tài liệu trên php.net đã chơi một trò đùa độc ác. Bạn sẽ không tìm thấy câu trả lời chính xác ở đó... tất nhiên trừ khi bạn cuộn đến cuối, vị trí trong phần "Ghi chú đóng góp của người dùng" sẽ trung thành và câu trả lời bị đánh giá thấp một cách không công bằng.

Có, tên tùy chọn chính xác là memcached.sess_binary_protocol. Nó phải bị vô hiệu hóa, sau đó các phiên sẽ bắt đầu hoạt động. Tất cả những gì còn lại là đặt thùng chứa mcrouter vào một nhóm có PHP!

Kết luận

Do đó, chỉ với những thay đổi về cơ sở hạ tầng, chúng tôi đã có thể giải quyết được vấn đề: vấn đề về khả năng chịu lỗi của memcached đã được giải quyết và độ tin cậy của bộ nhớ đệm đã được tăng lên. Ngoài những lợi thế rõ ràng cho ứng dụng, điều này còn mang lại khả năng cơ động khi làm việc trên nền tảng: khi tất cả các thành phần đều có dự trữ, công việc của quản trị viên được đơn giản hóa rất nhiều. Đúng, phương pháp này cũng có nhược điểm, có thể trông giống như một “cái nạng”, nhưng nếu nó tiết kiệm tiền, chôn vùi vấn đề và không gây ra vấn đề mới - tại sao không?

PS

Đọc thêm trên blog của chúng tôi:

Nguồn: www.habr.com

Thêm một lời nhận xét