-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcaching-strategy.mdc
More file actions
198 lines (152 loc) · 6.08 KB
/
Copy pathcaching-strategy.mdc
File metadata and controls
198 lines (152 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
---
description: 快取策略
globs:
alwaysApply: false
---
# 快取策略
本專案採用三層式快取架構,確保性能和數據一致性。
## 快取層級
1. **邊緣快取 (Edge Cache)** - 可選的前端或邊緣快取
2. **Redis 快取** - 主要快取層,存儲格式化後的 `BalanceResponse` 數據
3. **MongoDB 持久層** - 使用 `PortfolioSnapshot` 模型存儲數據,具有 TTL 功能
## 數據格式轉換
- MongoDB 中使用 `PortfolioSnapshot` 格式存儲
- 當返回給客戶端時,會轉換為 `BalanceResponse` 格式
- [src/core/cache/cache-mongo.service.ts](mdc:src/core/cache/cache-mongo.service.ts) 負責在查詢時進行格式轉換
## 過期機制
### MongoDB TTL
- `PortfolioSnapshot` 模型包含 `expiresAt` 字段,默認 30 分鐘後過期
- MongoDB TTL 索引會自動刪除過期記錄
- 查詢時只返回未過期的記錄
- 所有新記錄都必須有過期時間,並通過驗證確保過期時間在當前時間之後
```typescript
@Prop({
type: Date,
required: true,
default: function () {
const now = new Date();
now.setMinutes(now.getMinutes() + 30); // 預設 30 分鐘後過期
return now;
},
validate: {
validator: function (v: Date) {
return v > new Date(); // 確保過期時間在當前時間之後
},
message: '過期時間必須在當前時間之後',
},
})
expiresAt: Date;
```
### Redis TTL
- Redis 快取通常設置單獨的 TTL
- 可以在 API 調用時通過 `ttlSeconds` 參數自定義
## 快取失效
通過事件機制處理快取失效:
1. 當 webhook 事件到達時,發送 `ADDRESS_ACTIVITY` 通知事件
2. `CacheMongoService` 監聽這些事件並執行:
- 使用 `CacheKeyService` 清除 Redis 快取
- 使用 `DbService.invalidateAddressSnapshot()` 設置 MongoDB 記錄的 `expiresAt` 為當前時間
通過這種機制,確保了當數據變更時,快取能夠及時失效,同時避免了循環依賴問題。
## 架構概述
系統採用三層數據架構設計:
1. **Edge Cache** - API層面的緩存,最快速的訪問
2. **Redis** - 中間層緩存,跨服務共享資料
3. **MongoDB** - 持久化存儲,支援歷史記錄和複雜查詢
## 核心服務
- [CacheService](mdc:src/core/cache/cache.service.ts) - 提供通用緩存操作,支援Redis和記憶體模式
- [CacheKeyService](mdc:src/core/cache/cache-key.service.ts) - 管理統一的緩存鍵生成與解析
- [CacheMongoService](mdc:src/core/cache/cache-mongo.service.ts) - 負責Redis緩存與MongoDB數據的同步
- [NotificationService](mdc:src/notification/notification.service.ts) - 事件發布系統,用於解耦緩存操作和MongoDB同步
- [PortfolioMongoListener](mdc:src/core/portfolio/portfolio-mongo.listener.ts) - 監聽投資組合更新事件,負責MongoDB同步
## 事件驅動的緩存同步
系統使用事件驅動架構實現緩存與資料庫操作的解耦:
1. 資料更新時,先更新Redis緩存
2. 然後觸發事件(如`PORTFOLIO_UPDATE`)
3. 專門的監聽器(如`PortfolioMongoListener`)接收事件並更新MongoDB
```mermaid
graph LR
A[API請求] --> B[CacheMongoService]
B --> C[Redis更新]
B --> D[觸發事件]
D --> E[PortfolioMongoListener]
E --> F[MongoDB更新]
```
## 統一緩存鍵結構
系統使用統一的緩存鍵結構,格式為:
```
prefix:chain:chainId:address:provider:extra
```
示例:`portfolio:ethereum:1:0x1234:alchemy`
## MongoDB模型設計
主要MongoDB集合:
- [PortfolioSnapshot](mdc:src/core/db/schemas/portfolio-snapshot.schema.ts) - 儲存最新餘額快照
- [PortfolioHistory](mdc:src/core/db/schemas/portfolio-history.schema.ts) - 時間序列集合,記錄歷史變化
- [TxHistory](mdc:src/core/db/schemas/tx-history.schema.ts) - 交易歷史記錄
- [NftOwner](mdc:src/core/db/schemas/nft-owner.schema.ts) - NFT所有權記錄
- [NftMeta](mdc:src/core/db/schemas/nft-meta.schema.ts) - NFT元數據存儲
- [PriceCache](mdc:src/core/db/schemas/price-cache.schema.ts) - 幣價緩存
## TTL與索引策略
- Redis緩存默認過期時間為30分鐘(可配置)
- MongoDB使用TTL索引,30天無更新自動刪除記錄
- 所有集合使用相同的主要索引字段(chainId, address, provider)以保持與Redis鍵一致
- PortfolioHistory使用MongoDB的時間序列集合功能,支援高效時間範圍查詢
## 最佳實踐
1. **使用NotificationService觸發事件**,而不是直接調用數據操作
2. **Global模塊設計**:NotificationModule和CacheModule均設為全局模塊,避免循環依賴
3. **分離關注點**:緩存操作與MongoDB同步解耦,提高系統擴展性和可維護性
4. **一致性考慮**:清除緩存時同時觸發地址活動事件,確保各系統組件資料一致性
## 緩存策略使用範例
### 讀取資料流程
```typescript
// 使用CacheMongoService獲取數據(優先Redis,再MongoDB)
const data = await cacheMongoService.getPortfolioData(
chain,
chainId,
address,
provider,
);
// 如果沒有緩存數據,從區塊鏈獲取
if (!data) {
const blockchainData = await chainService.getBalances(address);
// 更新到緩存和MongoDB
await cacheMongoService.syncPortfolioData(
chain,
chainId,
address,
blockchainData,
provider,
30 * 60
);
return blockchainData;
}
return data;
```
### 更新資料流程
```typescript
// 1. 更新Redis緩存
await cacheService.set(cacheKey, portfolioData, ttlSeconds);
// 2. 觸發事件,解耦MongoDB同步操作
notificationService.emitPortfolioUpdate(
chain,
chainId,
address,
portfolioData,
provider,
ttlSeconds
);
```
### 失效緩存流程
```typescript
// 1. 刪除Redis緩存
const deletedCount = await cacheService.deleteByPattern(pattern);
// 2. 發送地址活動事件,用於通知其他服務
notificationService.emitAddressActivity(chain, chainId, address, {
action: 'cache_invalidated',
timestamp: new Date(),
});
```
## 錯誤處理和重連
系統實現了完整的錯誤處理和自動重連機制:
- 監聽Redis錯誤事件,自動嘗試重連
- MongoDB操作錯誤不影響Redis緩存操作
- 事件處理器獨立捕獲異常,避免事件傳播中斷