Hosting và scaling SignalR trong môi trường production
Bởi Andrew Stanton-Nurse, Brady Gaster và Tom Dykstra
Bài viết này giải thích các cân nhắc về hosting (lưu trữ) và scaling (mở rộng quy mô) cho các ứng dụng lưu lượng cao sử dụng ASP.NET Core SignalR.
Sticky sessions (Phiên cố định)
SignalR yêu cầu cùng một tiến trình máy chủ xử lý tất cả các yêu cầu HTTP cho một kết nối cụ thể. Khi SignalR chạy trên server farm (nhiều máy chủ), phải sử dụng "sticky sessions" (phiên cố định). "Sticky sessions" còn được gọi là session affinity (sự ái lực phiên). Azure App Service sử dụng Microsoft Application Request Routing (ARR) để định tuyến yêu cầu. Bật cài đặt "Session affinity" (ARR Affinity) trong ứng dụng App Service sẽ bật sticky sessions.
Có ba tình huống không cần sticky sessions cho một ứng dụng:
- Hosting trên một máy chủ đơn trong một tiến trình đơn
- Sử dụng Azure SignalR Service (sticky sessions được bật cho dịch vụ, không phải ứng dụng)
- Tất cả client được cấu hình chỉ sử dụng WebSockets và cấu hình client bật SkipNegotiation
Trong tất cả các trường hợp khác (bao gồm khi sử dụng Redis backplane), môi trường máy chủ phải được cấu hình cho sticky sessions.
Tài nguyên kết nối TCP
Số lượng kết nối TCP đồng thời mà một máy chủ web có thể hỗ trợ là có giới hạn. HTTP client tiêu chuẩn sử dụng các kết nối tạm thời (ephemeral). Các kết nối này có thể đóng khi client không hoạt động và mở lại sau. Mặt khác, kết nối SignalR là bền vững (persistent). Các kết nối SignalR vẫn mở ngay cả khi client không hoạt động. Trong một ứng dụng lưu lượng cao phục vụ nhiều client, các kết nối bền vững này có thể khiến các máy chủ đạt đến số lượng kết nối tối đa.
Các kết nối bền vững cũng tiêu thụ thêm bộ nhớ để theo dõi mỗi kết nối.
Việc sử dụng tài nguyên liên quan đến kết nối nhiều của SignalR có thể ảnh hưởng đến các ứng dụng web khác được lưu trữ trên cùng máy chủ. Khi SignalR mở và giữ các kết nối TCP cuối cùng còn lại, các ứng dụng web khác trên cùng máy chủ cũng không có thêm kết nối nào.
Nếu máy chủ hết kết nối, bạn sẽ thấy lỗi socket ngẫu nhiên và lỗi đặt lại kết nối. Ví dụ:
An attempt was made to access a socket in a way forbidden by its access permissions...
Để giữ cho việc sử dụng tài nguyên SignalR không gây ra lỗi trong các ứng dụng web khác, hãy chạy SignalR trên các máy chủ khác với các ứng dụng web khác của bạn.
Để giữ cho việc sử dụng tài nguyên SignalR không gây ra lỗi trong ứng dụng SignalR, hãy scale out để giới hạn số lượng kết nối mà một máy chủ phải xử lý.
Scale out (Mở rộng theo chiều ngang)
Một ứng dụng sử dụng SignalR cần theo dõi tất cả các kết nối của mình, điều này tạo ra vấn đề cho server farm. Thêm một máy chủ và nó nhận các kết nối mới mà các máy chủ khác không biết. Ví dụ, SignalR trên mỗi máy chủ trong sơ đồ sau không biết về các kết nối trên các máy chủ khác. Khi SignalR trên một trong các máy chủ muốn gửi tin nhắn đến tất cả client, tin nhắn chỉ đến với các client kết nối đến máy chủ đó.
!Minh họa mở rộng SignalR không có backplane
Các tùy chọn để giải quyết vấn đề này là Azure SignalR Service và Redis backplane.
Azure SignalR Service
Azure SignalR Service hoạt động như một proxy cho lưu lượng real-time và đồng thời là backplane khi ứng dụng được scale out trên nhiều máy chủ. Mỗi khi client khởi tạo kết nối đến máy chủ, client được chuyển hướng để kết nối đến dịch vụ. Sơ đồ sau minh họa quy trình này:
!Minh họa thiết lập kết nối đến Azure SignalR Service
Kết quả là dịch vụ quản lý tất cả các kết nối client, trong khi mỗi máy chủ chỉ cần một số lượng kết nối nhỏ cố định đến dịch vụ, như được hiển thị trong sơ đồ sau:
!Minh họa client và máy chủ kết nối đến dịch vụ
Cách tiếp cận scale out này có một số lợi thế so với Redis backplane:
- Sticky sessions, còn được gọi là client affinity, không bắt buộc vì client được chuyển hướng ngay lập tức đến Azure SignalR Service khi kết nối.
- Ứng dụng SignalR có thể scale out dựa trên số lượng tin nhắn được gửi, trong khi Azure SignalR Service có thể xử lý bất kỳ số lượng kết nối nào. Ví dụ, có thể có hàng nghìn client, nhưng nếu chỉ gửi vài tin nhắn mỗi giây, ứng dụng SignalR không cần scale out sang nhiều máy chủ chỉ để xử lý các kết nối.
- Ứng dụng SignalR không sử dụng nhiều tài nguyên kết nối hơn ứng dụng web không có SignalR.
Vì những lý do này, khuyến nghị sử dụng Azure SignalR Service cho tất cả ứng dụng ASP.NET Core SignalR được lưu trữ trên Azure, bao gồm App Service, máy ảo và container.
Để biết thêm thông tin, xem tài liệu Azure SignalR Service.
Redis backplane
Redis là kho lưu trữ key-value trong bộ nhớ hỗ trợ hệ thống tin nhắn với mô hình publish/subscribe (xuất bản/đăng ký). Redis backplane của SignalR sử dụng tính năng publish/subscribe để chuyển tiếp tin nhắn đến các máy chủ khác. Khi client tạo kết nối, thông tin kết nối được truyền đến backplane. Khi máy chủ muốn gửi tin nhắn đến tất cả client, nó gửi đến backplane. Backplane biết tất cả client kết nối và máy chủ nào họ đang kết nối. Nó gửi tin nhắn đến tất cả client thông qua máy chủ tương ứng. Quy trình này được minh họa trong sơ đồ sau:
!Minh họa Redis backplane với tin nhắn được gửi từ một máy chủ đến tất cả client
Redis backplane là cách tiếp cận scale out được khuyến nghị cho các ứng dụng được lưu trữ trên cơ sở hạ tầng của riêng bạn. Nếu có độ trễ kết nối đáng kể giữa trung tâm dữ liệu của bạn và trung tâm dữ liệu Azure, Azure SignalR Service có thể không phải là lựa chọn thực tế cho các ứng dụng on-premises có yêu cầu độ trễ thấp hoặc thông lượng cao.
Các lợi thế của Azure SignalR Service được mô tả trước đó là bất lợi cho Redis backplane:
- Sticky sessions bắt buộc, ngoại trừ khi cả hai điều sau đây đều đúng:
- Tất cả client được cấu hình chỉ sử dụng WebSockets.
- Cài đặt SkipNegotiation được bật trong cấu hình client. Sau khi kết nối được khởi tạo trên một máy chủ, kết nối phải ở lại trên máy chủ đó.
- Ứng dụng SignalR phải scale out dựa trên số lượng client, ngay cả khi gửi ít tin nhắn.
- Ứng dụng SignalR sử dụng nhiều tài nguyên kết nối hơn ứng dụng web không có SignalR.
Giới hạn IIS trên hệ điều hành client Windows
Windows 10 và Windows 8.x là hệ điều hành client. IIS (Internet Information Services) trên hệ điều hành client có giới hạn 10 kết nối đồng thời. Các kết nối SignalR có các đặc điểm sau:
- Chúng là tạm thời và thường được thiết lập lại.
- Chúng không bị hủy ngay lập tức khi không còn được sử dụng.
Những đặc điểm này làm cho việc đạt giới hạn 10 kết nối trên hệ điều hành client rất có thể xảy ra. Khi sử dụng hệ điều hành client để phát triển, hãy xem xét các khuyến nghị sau:
- Tránh IIS
- Sử dụng Kestrel hoặc IIS Express làm mục tiêu triển khai
Linux với Nginx
Code sau chứa các cài đặt tối thiểu cần thiết để bật WebSockets, ServerSentEvents và LongPolling cho SignalR:
http {
map $http_connection $connection_upgrade {
"~*Upgrade" $http_connection;
default keep-alive;
}
server {
listen 80;
server_name example.com *.example.com;
# Configure the SignalR Endpoint
location /hubroute {
# App server url
proxy_pass http://localhost:5000;
# Configuration for WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache off;
# WebSockets were implemented after http/1.0
proxy_http_version 1.1;
# Configuration for ServerSentEvents
proxy_buffering off;
# Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds
proxy_read_timeout 100s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}Khi sử dụng nhiều máy chủ backend, phải thêm sticky sessions để ngăn các kết nối SignalR chuyển đổi máy chủ khi kết nối. Có nhiều cách để thêm sticky sessions trong Nginx. Hai ví dụ sau đây dựa trên những gì bạn có sẵn.
Code sau bổ sung cấu hình ví dụ trước. Trong các đoạn code, backend là tên của nhóm máy chủ.
- Với Nginx Open Source, sử dụng
ip_hashđể định tuyến kết nối đến một máy chủ dựa trên địa chỉ IP của client:
```nginx http { upstream backend { # App server 1 server localhost:5000; # App server 2 server localhost:5002;
ip_hash; } } ```
- Với Nginx Plus, sử dụng
stickyđể thêm cookie vào yêu cầu và gắn kết yêu cầu người dùng với một máy chủ:
```nginx http { upstream backend { # App server 1 server localhost:5000; # App server 2 server localhost:5002;
sticky cookie srv_id expires=max domain=.example.com path=/ httponly; } } ```
- Đối với cả hai cấu hình, hãy thay đổi
proxy_pass http://localhost:5000trong phầnserverthànhproxy_pass http://backend.
Các nhà cung cấp SignalR backplane khác
Các nhà cung cấp không phải Microsoft sau đây cũng cung cấp SignalR backplane: