388 lines
11 KiB
Markdown
388 lines
11 KiB
Markdown
# 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
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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+)
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```csharp
|
|
builder.Services.AddHealthChecks()
|
|
.AddNpgSql(connectionString, name: "postgresql")
|
|
.AddRedis(redisConnectionString, name: "redis")
|
|
.AddSignalRHub<ChatHub>(name: "signalr-hub");
|
|
```
|
|
|
|
## Tài Liệu Tham Khảo
|
|
|
|
- [ASP.NET Core SignalR](https://docs.microsoft.com/en-us/aspnet/core/signalr/)
|
|
- [SignalR Scale-out with Redis](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane)
|
|
- [X3DH Protocol Specification](https://signal.org/docs/specifications/x3dh/)
|
|
- [Azure SignalR Service](https://docs.microsoft.com/en-us/azure/azure-signalr/)
|