Add XCode skills for entity caching, ORM, and sharding ETL
|
---
name: xcode-sharding-etl
description: >
使用 NewLife.XCode å®žçŽ°åˆ†åº“åˆ†è¡¨å’Œæ•°æ® ETL/åŒæ¥ï¼Œæ¶µç›– TimeShardPolicy 时间分片ç–ç•¥é…ç½®
(TablePolicy/ConnPolicy/Step)ã€Meta.ShardPolicy 挂载ã€AutoShard 跨分片查询ã€
EntitySplit 手动切æ¢åˆ†ç‰‡ï¼Œä»¥åŠ ETL<TSource> æ•°æ®æŠ½å–æ¡†æž¶ï¼ˆIExtracter 五ç§å®žçŽ°ï¼‰
å’Œ SyncManager 四阶段数æ®åŒæ¥ã€‚
适用于大数æ®åˆ†è¡¨å˜å‚¨ã€è·¨åˆ†ç‰‡èšåˆæŸ¥è¯¢ã€æ•°æ®è¿ç§»ã€å¢žé‡åŒæ¥ç‰åœºæ™¯ã€‚
argument-hint: >
说明数æ®è§„模和分表ç–略(按天/月/年);是å¦éœ€è¦é›ªèб ID 路由;
是å¦éœ€è¦è·¨åˆ†ç‰‡èšåˆæŸ¥è¯¢ï¼›ETL æŠ½å–æ˜¯å¢žé‡è¿˜æ˜¯å…¨é‡ï¼›æ•°æ®åŒæ¥çš„æºç«¯å’Œç›®æ ‡ç«¯ç±»åž‹ã€‚
---
# XCode 分表分库与 ETL
## 分表分库(Shards)
### 适用场景
- å•表数æ®é‡è¶…过åƒä¸‡ï¼Œéœ€è¦æŒ‰æ—¶é—´ç»´åº¦ï¼ˆå¤©/月/年)分表。
- 需è¦è·¨åˆ†ç‰‡æŸ¥è¯¢åކ岿•°æ®å¹¶æ±‡æ€»ç»“果。
- 使用雪花 ID(Int64)作为主键,通过 ID 内嵌的时间å移路由到æ£ç¡®åˆ†ç‰‡ã€‚
### TimeShardPolicy é…ç½®
```csharp
// å®žä½“é™æ€æž„é€ å‡½æ•°ä¸æŒ‚载分片ç–ç•¥
static AccessLog()
{
// 按天分表(å•库)
Meta.ShardPolicy = new TimeShardPolicy(nameof(CreateTime), Meta.Factory)
{
TablePolicy = "{0}_{1:yyyyMMdd}", // è¡¨åæ ¼å¼ï¼šAccessLog_20250101
Step = TimeSpan.FromDays(1), // æ¯ 1 å¤©ä¸€å¼ è¡¨
};
}
static Order()
{
// 按月分库分表(分库 + 分表)
Meta.ShardPolicy = new TimeShardPolicy(nameof(Id), Meta.Factory)
{
ConnPolicy = "{0}_{1:yyyy}", // åº“åæ ¼å¼ï¼šOrder_2025
TablePolicy = "{0}_{1:yyyyMM}", // è¡¨åæ ¼å¼ï¼šOrder_202501
Step = TimeSpan.FromDays(30), // æ¯ 30 å¤©ï¼ˆçº¦ä¸€æœˆï¼‰ä¸€å¼ è¡¨
};
}
static EventLog()
{
// 按年分表
Meta.ShardPolicy = new TimeShardPolicy(nameof(CreateTime), Meta.Factory)
{
TablePolicy = "{0}_{1:yyyy}",
Step = TimeSpan.FromDays(365),
};
}
```
**`TimeShardPolicy` 傿•°è¯´æ˜Ž**:
| 傿•° | 说明 |
|------|------|
| ç¬¬ä¸€å‚æ•°ï¼ˆå—段å)| åˆ†ç‰‡ä¾æ®å—段:`DateTime` å—æ®µå 或 雪花 `Int64` å—æ®µå |
| `Factory` | 实体工厂(`Meta.Factory`)|
| `TablePolicy` | è¡¨åæ ¼å¼ï¼Œ`{0}` = 实体表å,`{1}` = æ—¶é—´ |
| `ConnPolicy` | åº“åæ ¼å¼ï¼ˆåˆ†åº“æ—¶é…置),`{0}` = 连接å,`{1}` = æ—¶é—´ |
| `Step` | æ¯ä¸ªåˆ†ç‰‡çš„æ—¶é—´è·¨åº¦ |
### æ•°æ®å†™å…¥ï¼ˆè‡ªåŠ¨è·¯ç”±ï¼‰
æ¡†æž¶æ ¹æ®å®žä½“çš„åˆ†ç‰‡å—æ®µå€¼è‡ªåŠ¨è·¯ç”±åˆ°æ£ç¡®çš„表/库:
```csharp
// è‡ªåŠ¨è·¯ç”±å†™å…¥å¯¹åº”åˆ†ç‰‡ï¼ˆæ— éœ€æ‰‹åŠ¨æŒ‡å®šï¼‰
var log = new AccessLog { CreateTime = DateTime.Now, ... };
log.Insert(); // 自动写入 AccessLog_20250402 表
// æ‰¹é‡ Insert 自动按分片分组写入
var logs = batchLogs;
logs.Insert(); // 自动分组,æ¯ç»„写入对应分片
```
### AutoShard — 跨分片查询
跨多个分片自动执行查询并汇总结果:
```csharp
// 按时间区间跨分片查询(自动推导涉åŠçš„分片)
var start = new DateTime(2025, 1, 1);
var end = new DateTime(2025, 3, 31);
var list = AccessLog.Meta.AutoShard(start, end, (session) =>
{
// 在æ¯ä¸ªåˆ†ç‰‡ session 䏿‰§è¡Œçš„æŸ¥è¯¢
return AccessLog.FindAll(AccessLog._.CreateTime.Between(start, end));
});
// 跨分片统计
var total = AccessLog.Meta.AutoShard(start, end, session =>
(Int64)AccessLog.FindCount()
).Sum();
```
**自动æ¡ä»¶è£å‰ª**:å•åˆ†ç‰‡å‘½ä¸æ—¶è‡ªåŠ¨ç§»é™¤å†—ä½™æ—¶é—´æ¡ä»¶ï¼Œå‡å°‘ SQL 扫æèŒƒå›´ã€‚
### EntitySplit — 手动切æ¢åˆ†ç‰‡
精准指定连接和表å(è¿ç»´è¿ç§»ã€æ‰‹åŠ¨å½’æ¡£åœºæ™¯ï¼‰ï¼š
```csharp
// 手动切æ¢åˆ°æŒ‡å®šåˆ†ç‰‡ï¼ˆusing å—内的查询都针对该分片)
using (Order.Meta.CreateSplit("Order_2024", "Order_202412"))
{
var orders = Order.FindAll(Order._.Status == 1);
}
// 按ç–略路由(输入时间,让ç–ç•¥è®¡ç®—ç›®æ ‡åˆ†ç‰‡ï¼‰
using (Order.Meta.CreateShard(new DateTime(2024, 12, 15)))
{
var order = Order.FindByKey(orderId);
}
```
---
## ETL æ•°æ®æŠ½å–æ¡†æž¶
### 适用场景
- 大表增é‡åŒæ¥ï¼ˆæŒ‰æ—¶é—´å—段滑动窗å£åˆ†æ‰¹æŠ½å–)。
- åŽ†å²æ•°æ®å…¨é‡è¿ç§»ï¼ˆåˆ†é¡µæˆ–按 ID 区间)。
- 跨库数æ®è½¬æ¢å¤„ç†ï¼ˆæºç«¯ â‰ ç›®æ ‡ç«¯æ ¼å¼ï¼‰ã€‚
### ETL<TSource> 基类
```csharp
// 继承 ETL 实现自定义处ç†
public class OrderETL : ETL<Order>
{
/// <summary>处ç†ä¸€æ‰¹æ•°æ®</summary>
protected override Int32 Process(IList<Order> list, DataContext ctx)
{
foreach (var order in list)
{
// 转æ¢å¹¶å†™å…¥ç›®æ ‡
var stat = BuildStat(order);
stat.Save();
}
return list.Count;
}
}
// é…置并å¯åЍ
var etl = new OrderETL
{
Setting = new ExtractSetting
{
Start = new DateTime(2025, 1, 1), // 抽å–èµ·å§‹æ—¶é—´
End = DateTime.Today, // æŠ½å–æˆªæ¢æ—¶é—´
BatchSize = 1000, // æ¯æ‰¹æ•°é‡
Step = TimeSpan.FromMinutes(10), // æ—¶é—´çª—å£æ¥é•¿ï¼ˆTimeExtracter)
}
};
etl.Start();
```
### IExtracter 抽å–器æ—è°±
| 抽å–器 | 场景 | 特点 |
|-------|------|------|
| `TimeExtracter` | **增é‡åŒæ¥**(默认)| æŒ‰æ—¶é—´å—æ®µé€’å¢žï¼Œæ¸¸æ ‡æ»‘åŠ¨ |
| `TimeSpanExtracter` | è¡¥è·‘åŽ†å² | 固定时间æ¥é•¿å¾ªçŽ¯ï¼Œå¯é‡å¤æ‰§è¡Œ |
| `PagingExtracter` | å…¨é‡è¡¨ï¼ˆæ— æ—¶é—´å—æ®µï¼‰| Row å移分页 |
| `IdExtracter` | 自增主键 | æŒ‰è¿žç» ID 区间分批 |
| `EntityIdExtracter` | å¤åˆä¸»é”® | 按实体主键分批 |
```csharp
// 默认使用 TimeExtracter(推è增é‡åœºæ™¯ï¼‰
var etl = new OrderETL();
// 指定抽å–器
var etl2 = new OrderETL
{
Extracter = new PagingExtracter { BatchSize = 500 }
};
// IdExtracter(适åˆè‡ªå¢ž ID 表)
var etl3 = new OrderETL
{
Extracter = new IdExtracter { BatchSize = 1000 }
};
```
### ExtractSetting é…ç½®
```csharp
var setting = new ExtractSetting
{
Start = new DateTime(2024, 1, 1), // 抽å–èµ·å§‹æ—¶é—´/ID
End = DateTime.Now, // æŠ½å–æˆªæ¢
Offset = TimeSpan.FromMinutes(-5), // 安全å移(é¿å…æŠ½å–æœªå®Œæˆå†™å…¥çš„æ•°æ®ï¼‰
BatchSize = 1000, // æ¯æ‰¹è®°å½•æ•°
Step = TimeSpan.FromHours(1), // æ¯æ¥æ—¶é—´è·¨åº¦ï¼ˆTimeExtracter)
};
```
### IETLModule 生命周期钩å
```csharp
public class MyModule : IETLModule
{
public void OnInit(ETL etl) { /* åˆå§‹åŒ–èµ„æº */ }
public void OnProcessing(ETL etl, DataContext ctx)
{
// æ¯æ‰¹æ•°æ®å¤„ç†å‰åŽçš„逻辑(统计/日志/报è¦ï¼‰
ctx.TotalCount // ç´¯è®¡å¤„ç†æ€»æ•°
ctx.Speed // 当å‰å¤„ç†é€Ÿåº¦ï¼ˆæ¡/秒)
}
public void OnStop(ETL etl) { /* é‡Šæ”¾èµ„æº */ }
}
etl.Modules.Add(new MyModule());
```
---
## SyncManager — æ•°æ®åŒæ¥æ¡†æž¶
### 适用场景
- 主从库数æ®åŒå‘åŒæ¥ï¼ˆå¦‚主数æ®ä¸å¿ƒ → 分支机构)。
- 业务系统间数æ®åŒæ¥ï¼ˆä¿æŒå¤šä¸ªæ•°æ®åº“䏿Ÿå¼ 表的一致性)。
- 需è¦å†²çªæ£€æµ‹å’Œè§£å†³ç–略的场景。
### å››é˜¶æ®µåŒæ¥æµç¨‹
```
ProcessNew 从方新增处ç†ï¼ˆé¿å…主键冲çªï¼Œå…ˆåŒæ¥ä¸»æ–¹è¿˜æ²¡æœ‰çš„æ–°æ•°æ®ï¼‰
↓
ProcessDelete ä»Žæ–¹åˆ é™¤å¤„ç†ï¼ˆæ¸…ç†å·²åœ¨ä¸»æ–¹åˆ 除的数æ®ï¼‰
↓
ProcessItems ä¸»æ–¹å¢žé‡æ•°æ®ï¼ˆåˆ†æ‰¹æ‹‰å–,更新从方)
↓
ProcessOthers 检查本地未涉åŠçš„æ•°æ®åœ¨ä¸»æ–¹æ˜¯å¦ä»å˜åœ¨
```
### ISyncMaster / ISyncSlave
```csharp
// 主方:实现 ISyncMasterï¼ˆæ•°æ®æä¾›è€…ï¼‰
public class OrderMaster : ISyncMaster
{
// èŽ·å–æŒ‡å®šæ—¶é—´åŽä¿®æ”¹çš„主键集åˆ
public IList<Object> GetAdd(DateTime last, Int32 count) =>
Order.FindAll(Order._.UpdateTime >= last, Order._.Id, "Id", 0, count)
.Select(e => (Object)e.Id).ToList();
// 获å–å·²åˆ é™¤çš„ä¸»é”®é›†åˆï¼ˆéœ€è¦é¢å¤–çš„åˆ é™¤æ—¥å¿—è¡¨ï¼‰
public IList<Object> GetDelete(DateTime last) => [];
// 获å–一批完整数æ®
public IList<IEntity> GetItems(IList<Object> keys) =>
Order.FindAll(Order._.Id.In(keys)).Cast<IEntity>().ToList();
}
// 从方:实现 ISyncSlaveï¼ˆæ•°æ®æ¶ˆè´¹è€…)
// 通常用泛型适é…å™¨ï¼Œå¤„ç† LastSync / SyncStatus å—æ®µ
```
### SyncManager é…置与å¯åЍ
```csharp
var sync = new SyncManager
{
Master = new OrderMaster(), // 主方
BatchSize = 200, // æ¯æ‰¹å¤„ç†æ•°
UpdateConflictByLastUpdate = true, // å†²çªæ—¶æ¯”较 UpdateTime(true=本地时间新则ä¸è¦†ç›–)
Names = new[] { "Status", "Amount", "UpdateTime" }, // å‚ä¸ŽåŒæ¥çš„å—æ®µ
};
sync.Start(); // å¼€å§‹åŒæ¥
// sync.Stop(); // åœæ¢
```
### 冲çªè§£å†³ç–ç•¥
| 场景 | `UpdateConflictByLastUpdate=false`(默认主覆盖)| `=true`(按时间戳)|
|------|------|------|
| 从方改ã€ä¸»æ–¹ä¸å˜ | 主方覆盖从方 | 从方推é€åˆ°ä¸»æ–¹ |
| 从方ä¸å˜ã€ä¸»æ–¹æ”¹ | 主方更新从方 | 主方更新从方 |
| åŒæ–¹åŒæ—¶ä¿®æ”¹ | 主方覆盖从方 | 比较时间戳,较新者胜 |
### ä»Žæ–¹è¾…åŠ©å—æ®µ
```xml
<!-- åŒæ¥æ¡†æž¶ä½¿ç”¨çš„è¾…åŠ©å—æ®µï¼ˆåœ¨ Model.xml 䏿·»åŠ ï¼‰ -->
<Column Name="LastSync" DataType="DateTime" Description="æœ€è¿‘åŒæ¥æ—¶é—´" />
<Column Name="SyncStatus" DataType="Int32" Description="åŒæ¥çжæ€ã€‚1=æ–°å¢žå¾…åŒæ¥ 2=åˆ é™¤å¾…åŒæ¥" />
```
## 注æ„事项
- 分表实体的 `InitData` 䏿‰§è¡Œå»ºè¡¨æ£€æŸ¥ï¼ˆåˆ†ç‰‡è¡¨ç”±è¿è¡Œæ—¶æŒ‰ç–略创建),ä¸è¦åœ¨åˆ†ç‰‡å®žä½“ä¸åšæ•°æ®åˆå§‹åŒ–。
- `AutoShard` 跨分片查询是串行执行的(默认),大é‡åˆ†ç‰‡æ—¶æ€§èƒ½éšåˆ†ç‰‡æ•°çº¿æ€§å¢žåŠ ï¼›é€‚åˆæŸ¥è¯¢èŒƒå›´ç¡®å®šçš„场景。
- `TimeExtracter` ä½¿ç”¨æ»‘åŠ¨æ¸¸æ ‡ï¼Œæ–点ç»è·‘时从 `ExtractSetting.Start` 的上次ä½ç½®ç»§ç»ï¼Œæ³¨æ„æŒä¹…åŒ–æ¸¸æ ‡ä½ç½®ã€‚
- ETL `Process` 方法应åšå¹‚ç‰å¤„ç†ï¼ˆåŒä¸€æ‰¹æ•°æ®é‡å¤å¤„ç†ä¸äº§ç”Ÿå‰¯ä½œç”¨ï¼‰ï¼Œä»¥åº”对失败é‡è¯•。
|