11 KiB
11 KiB
Tài Liệu Kiến Trúc Chat Service
Kiến trúc chi tiết cho Chat Service với E2EE, SignalR, scalability patterns, và AI integration.
Tổng Quan Kiến Trúc
graph TB
subgraph "Clients"
WEB[Web App]
MOB[Mobile App]
BOT[Bot Client]
end
subgraph "Load Balancer"
LB[NGINX/Traefik]
SS[Sticky Sessions]
end
subgraph "Chat Service Instances"
CS1[Instance 1<br/>SignalR Hub]
CS2[Instance 2<br/>SignalR Hub]
CS3[Instance 3<br/>SignalR Hub]
end
subgraph "Backplane"
RD[(Redis<br/>Pub/Sub)]
end
subgraph "Storage"
PG[(PostgreSQL<br/>Messages)]
RC[(Redis<br/>Cache)]
end
subgraph "AI Service"
AI[OpenAI<br/>GPT-4]
end
WEB & MOB & BOT --> LB
LB --> SS --> CS1 & CS2 & CS3
CS1 & CS2 & CS3 <--> RD
CS1 & CS2 & CS3 --> PG
CS1 & CS2 & CS3 --> RC
CS1 & CS2 & CS3 --> AI
style LB fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
style RD fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style PG fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
style AI fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
Mã Hóa Đầu-cuối (E2EE)
Giao Thức Trao Đổi Khóa X3DH
Service triển khai giao thức Extended Triple Diffie-Hellman (X3DH) để thiết lập phiên mã hóa:
sequenceDiagram
participant Alice as Alice (Người gửi)
participant Server as Chat Server
participant Bob as Bob (Người nhận)
Note over Bob,Server: Bob đăng ký keys
Bob->>Server: Đăng ký Key Bundle<br/>(Identity, SignedPreKey, OneTimePreKeys)
Server->>Server: Lưu chỉ public keys
Note over Alice,Server: Alice khởi tạo session
Alice->>Server: Yêu cầu Key Bundle của Bob
Server->>Alice: {IdentityKey, SignedPreKey, OneTimePreKey}
Alice->>Alice: X3DH → Session Key
Alice->>Alice: AES-256-GCM mã hóa tin nhắn
Alice->>Server: Gửi tin nhắn đã mã hóa
Server->>Bob: Chuyển tin nhắn đã mã hóa
Bob->>Bob: X3DH → Session Key
Bob->>Bob: AES-256-GCM giải mã tin nhắn
style Server fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
Các Thành Phần Khóa
| Thành phần | Mô tả | Lưu trữ |
|---|---|---|
| Identity Key | Cặp khóa Curve25519 dài hạn | Private: Chỉ client |
| Signed Pre-Key | Xoay vòng mỗi 30 ngày | Public: Server |
| One-Time Pre-Keys | Tiêu thụ mỗi session | Public: Server (xóa sau khi dùng) |
| Session Key | Derive qua X3DH | Không lưu trữ |
Tính Năng Bảo Mật
- ✅ Forward Secrecy: Khóa bị lộ không tiết lộ tin nhắn cũ
- ✅ Zero-Knowledge Server: Server không thể giải mã tin nhắn
- ✅ Deniability: Không có bằng chứng mật mã về tác giả
Domain Model
Aggregate Roots
erDiagram
Conversation ||--o{ Message : contains
Conversation ||--o{ ConversationParticipant : has
ChatUser ||--o{ Conversation : participates
ChatUser ||--|| UserKeyBundle : has
ChatUser ||--o{ OneTimePreKey : owns
Conversation {
uuid Id PK
string Name
string AvatarUrl
enum Type "Direct|Group"
uuid CreatorId
datetime CreatedAt
datetime LastActivityAt
}
Message {
uuid Id PK
uuid ConversationId FK
uuid SenderId FK
string EncryptedContent
string Nonce
string AuthTag
enum Type "Text|Image|File|System"
enum Status "Sent|Delivered|Read|Failed"
datetime CreatedAt
}
ConversationParticipant {
uuid Id PK
uuid ConversationId FK
uuid ChatUserId FK
enum Role "Owner|Admin|Member"
datetime JoinedAt
datetime LastReadAt
}
ChatUser {
uuid Id PK
string IdentityUserId
string DisplayName
string AvatarUrl
enum Status "Online|Away|Offline"
datetime LastSeenAt
}
UserKeyBundle {
string IdentityPublicKey
string SignedPreKey
string SignedPreKeySignature
datetime SignedPreKeyTimestamp
}
OneTimePreKey {
uuid Id PK
int KeyId
string PublicKey
bool IsUsed
datetime CreatedAt
}
Domain Events
| Event | Trigger | Handler |
|---|---|---|
ConversationCreatedDomainEvent |
Hội thoại mới được tạo | Thông báo participants |
MessageSentDomainEvent |
Tin nhắn được gửi | Cập nhật last_activity, broadcast |
MessageDeliveredDomainEvent |
Tin nhắn đã gửi đến | Thông báo sender |
MessageReadDomainEvent |
Tin nhắn đã đọc | Thông báo sender |
UserJoinedRoomDomainEvent |
User tham gia room | Thông báo members |
UserLeftRoomDomainEvent |
User rời room | Thông báo members |
TypingDomainEvent |
User đang gõ | Broadcast to room |
ChatUserCreatedDomainEvent |
Chat user mới | Khởi tạo presence |
UserKeyBundleUpdatedDomainEvent |
Keys được cập nhật | Invalidate cached keys |
SignalR Hub Architecture
Connection Lifecycle
sequenceDiagram
participant Client
participant Hub as ChatHub
participant Groups
participant DB as Database
participant Redis
Client->>Hub: Connect (JWT Token)
Hub->>Hub: OnConnectedAsync()
Hub->>DB: Load danh sách hội thoại
Hub->>Groups: AddToGroupAsync(conversationId)
Hub->>Redis: Publish(UserOnline)
Hub-->>Client: Connected
Note over Client,Hub: User hiện đang online
Client->>Hub: SendMessage(conversationId, encryptedContent)
Hub->>DB: Lưu tin nhắn đã mã hóa
Hub->>Redis: Publish(NewMessage)
Redis-->>Hub: Broadcast đến tất cả instances
Hub->>Groups: Clients.Group(conversationId)
Hub-->>Client: ReceiveMessage
style Hub fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
style Redis fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style DB fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
Hub Methods
public class ChatHub : Hub<IChatHubClient>
{
// Quản lý kết nối
public override Task OnConnectedAsync();
public override Task OnDisconnectedAsync(Exception? exception);
// Quản lý phòng
public Task JoinRoom(Guid conversationId);
public Task LeaveRoom(Guid conversationId);
// Nhắn tin
public Task SendMessage(Guid conversationId, string encryptedContent,
string nonce, string? authTag);
public Task SendTypingIndicator(Guid conversationId, bool isTyping);
public Task MarkMessageRead(Guid conversationId, Guid messageId);
// Tích hợp AI
public IAsyncEnumerable<string> StreamAIResponse(Guid conversationId,
string prompt);
}
Scalability Architecture
Redis Backplane
graph LR
subgraph "Instance 1"
C1[Client A] --> H1[Hub 1]
end
subgraph "Instance 2"
C2[Client B] --> H2[Hub 2]
end
subgraph "Redis"
CH[Channel: chat.messages]
end
H1 -->|Publish| CH
CH -->|Subscribe| H2
H2 -->|Deliver| C2
style CH fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style H1 fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:2px
style H2 fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:2px
Cấu Hình Scaling
| Option | Ưu điểm | Nhược điểm |
|---|---|---|
| Redis Backplane | Đơn giản, on-premise | Cần quản lý Redis cluster |
| Azure SignalR | Serverless, không sticky sessions | Vendor lock-in, chi phí |
| Sticky Sessions | Đơn giản nhất | Không hoàn hảo cho failover |
AI Integration Flow
sequenceDiagram
participant User
participant Hub as ChatHub
participant AI as AIService
participant OpenAI
participant DB as Database
User->>Hub: "@gpt Giải thích DDD"
Hub->>DB: Load lịch sử (20 tin nhắn)
Hub->>AI: StreamAsync(prompt, history)
AI->>OpenAI: ChatCompletion (stream: true)
loop Streaming
OpenAI-->>AI: Token chunk
AI-->>Hub: yield chunk
Hub-->>User: ReceiveAIChunk
end
Hub->>DB: Lưu AI response
Hub-->>User: AIResponseComplete
style Hub fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
style AI fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
style OpenAI fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
Resiliency Patterns
Automatic Reconnect
stateDiagram-v2
[*] --> Connected
Connected --> Disconnected: Mất kết nối
Disconnected --> Reconnecting: Tự động retry
Reconnecting --> Connected: Thành công
Reconnecting --> Reconnecting: Retry với backoff
Reconnecting --> Disconnected: Vượt quá max retries
Disconnected --> [*]: User logout
Stateful Reconnect (.NET 8+)
// Server configuration
builder.Services.AddSignalR(options =>
{
options.StatefulReconnectBufferSize = 32 * 1024; // 32KB
});
// Với UseStatefulReconnect
app.MapHub<ChatHub>("/chatHub", options =>
{
options.AllowStatefulReconnects = true;
});
Deployment Architecture
Kubernetes với Sticky Sessions
apiVersion: apps/v1
kind: Deployment
metadata:
name: chatservice
spec:
replicas: 3
template:
spec:
containers:
- name: chatservice
image: chatservice:latest
ports:
- containerPort: 8080
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: chat-secrets
key: redis-url
---
apiVersion: v1
kind: Service
metadata:
name: chatservice
annotations:
service.beta.kubernetes.io/aws-load-balancer-sticky-sessions: "true"
spec:
type: LoadBalancer
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
ports:
- port: 80
targetPort: 8080
Health Checks
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, name: "postgresql")
.AddRedis(redisConnectionString, name: "redis")
.AddSignalRHub<ChatHub>(name: "signalr-hub");