# UDP数据推送与Redis缓存方案

## 1. 需求分析

当UDP数据到达时，需要将数据推送到一些外部接口，但这些接口所需的数据（如房间号）在UDP数据中并不包含，而是存储在数据库中。为了减少数据库查询开销，提高推送效率，需要：

1. 定期从数据库获取必要信息并更新到Redis
2. UDP数据到达时，从Redis快速获取所需信息
3. 实现高效的数据推送机制

## 2. 解决方案设计

### 2.1 定时任务设计

创建一个定时任务，定期从ApiController获取主机信息并更新到Redis：

```csharp
public class HostInfoSyncJob : IJob
{
    public void Execute()
    {
        try
        {
            // 创建HttpClient实例
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["CurrentUrl"]);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                
                // 调用API获取所有主机信息
                var response = client.GetAsync("api/GetAllHostInfo").Result;
                if (response.IsSuccessStatusCode)
                {
                    var hostInfos = response.Content.ReadAsAsync<List<HostInfoDto>>().Result;
                    
                    // 更新Redis缓存
                    foreach (var hostInfo in hostInfos)
                    {
                        // 缓存主机基本信息
                        string hostInfoKey = CacheKey.HostInfo_Key_HostNumber + "_" + hostInfo.HostNumber;
                        CSRedisCacheHelper.Forever<HostInfoDto>(hostInfoKey, hostInfo);
                        
                        // 缓存主机号到房间号的映射
                        string hostToRoomKey = CacheKey.HostToRoom_Mapping + "_" + hostInfo.HostNumber;
                        CSRedisCacheHelper.Forever<string>(hostToRoomKey, hostInfo.RoomNumber);
                        
                        // 缓存房间号到主机号的映射
                        string roomToHostKey = CacheKey.RoomToHost_Mapping + "_" + hostInfo.RoomNumber;
                        CSRedisCacheHelper.Forever<string>(roomToHostKey, hostInfo.HostNumber);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            log4net.LogManager.GetLogger(typeof(HostInfoSyncJob)).Error("同步主机信息到Redis失败: " + ex.Message);
        }
    }
}
```

### 2.2 ApiController扩展

在ApiController中添加获取所有主机信息的接口：

```csharp
/// <summary>
/// 获取所有主机信息，用于定时同步到Redis
/// </summary>
/// <returns></returns>
public ActionResult GetAllHostInfo()
{
    try
    {
        List<HostInfoDto> hostInfos = new List<HostInfoDto>();
        
        // 获取所有酒店
        var hotels = SysHotelManager.LoadAll();
        foreach (var hotel in hotels)
        {
            // 获取酒店下所有主机
            var hosts = HostManager.LoadAll(hotel.ID);
            foreach (var host in hosts)
            {
                HostInfoDto dto = new HostInfoDto
                {
                    HostNumber = host.HostNumber,
                    RoomNumber = host.RoomNumber,
                    HotelID = hotel.ID,
                    HotelCode = hotel.Code,
                    HotelName = hotel.Name,
                    Status = host.Status,
                    RoomStatus = host.RoomStatus?.Name,
                    MAC = host.MAC
                };
                hostInfos.Add(dto);
            }
        }
        
        return Json(hostInfos, JsonRequestBehavior.AllowGet);
    }
    catch (Exception ex)
    {
        logger.Error("获取所有主机信息失败: " + ex.Message);
        return Json(new List<HostInfoDto>(), JsonRequestBehavior.AllowGet);
    }
}
```

### 2.3 UDP数据处理与推送

在UDP数据处理模块中，从Redis获取所需信息并推送到外部接口：

```csharp
public class UdpDataPusher
{
    private static readonly string PushApiUrl = System.Configuration.ConfigurationManager.AppSettings["PushApiUrl"];
    
    public static void PushUdpData(string hostNumber, string udpData)
    {
        try
        {
            // 从Redis获取主机信息
            string hostInfoKey = CacheKey.HostInfo_Key_HostNumber + "_" + hostNumber;
            var hostInfo = CSRedisCacheHelper.Get<HostInfoDto>(hostInfoKey);
            
            if (hostInfo == null)
            {
                // 如果Redis中没有，从数据库获取并更新Redis
                hostInfo = GetHostInfoFromDatabase(hostNumber);
                if (hostInfo == null)
                {
                    log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("无法获取主机信息: " + hostNumber);
                    return;
                }
                // 更新到Redis
                CSRedisCacheHelper.Forever<HostInfoDto>(hostInfoKey, hostInfo);
            }
            
            // 构建推送数据
            PushData pushData = new PushData
            {
                HostNumber = hostNumber,
                RoomNumber = hostInfo.RoomNumber,
                HotelCode = hostInfo.HotelCode,
                HotelName = hostInfo.HotelName,
                UdpData = udpData,
                Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
            };
            
            // 推送到外部接口
            SendPushRequest(pushData);
        }
        catch (Exception ex)
        {
            log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("推送UDP数据失败: " + ex.Message);
        }
    }
    
    private static HostInfoDto GetHostInfoFromDatabase(string hostNumber)
    {
        // 创建HttpClient实例
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["CurrentUrl"]);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            
            // 调用API获取主机信息
            var response = client.GetAsync($"api/GetHostInfo?hostNumber={hostNumber}").Result;
            if (response.IsSuccessStatusCode)
            {
                return response.Content.ReadAsAsync<HostInfoDto>().Result;
            }
        }
        return null;
    }
    
    private static void SendPushRequest(PushData data)
    {
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri(PushApiUrl);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            
            var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
            var response = client.PostAsync("", content).Result;
            
            if (!response.IsSuccessStatusCode)
            {
                log4net.LogManager.GetLogger(typeof(UdpDataPusher)).Error("推送失败: " + response.StatusCode);
            }
        }
    }
}
```

### 2.4 缓存键设计

在CacheKey类中添加新的缓存键常量：

```csharp
public class CacheKey
{
    // 现有缓存键...
    
    // 新增缓存键
    public static string HostToRoom_Mapping = "HostToRoom_Mapping";
    public static string RoomToHost_Mapping = "RoomToHost_Mapping";
    public static string HostInfo_Key_HostNumber = "HostInfo_Key_HostNumber";
}
```

### 2.5 数据模型设计

```csharp
public class HostInfoDto
{
    public string HostNumber { get; set; }
    public string RoomNumber { get; set; }
    public int HotelID { get; set; }
    public string HotelCode { get; set; }
    public string HotelName { get; set; }
    public bool Status { get; set; }
    public string RoomStatus { get; set; }
    public string MAC { get; set; }
}

public class PushData
{
    public string HostNumber { get; set; }
    public string RoomNumber { get; set; }
    public string HotelCode { get; set; }
    public string HotelName { get; set; }
    public string UdpData { get; set; }
    public string Timestamp { get; set; }
}
```

### 2.6 定时任务注册

在Global.asax.cs中注册定时任务：

```csharp
protected override void Application_Start(object sender, EventArgs e)
{
    // 现有代码...
    
    // 注册定时任务
    JobManager.AddJob(
        () => new HostInfoSyncJob().Execute(),
        s => s.ToRunEvery(5).Minutes() // 每5分钟执行一次
    );
    
    // 现有代码...
}
```

## 3. 性能优化

### 3.1 缓存策略优化

1. **缓存过期时间**：对于主机信息，使用永久缓存，只有在定时任务执行时更新
2. **缓存预热**：系统启动时执行一次全量同步，确保Redis中有初始数据
3. **增量更新**：定时任务可以只更新发生变化的主机信息，减少网络传输

### 3.2 并发处理优化

1. **异步推送**：使用异步方式推送数据，避免阻塞UDP处理线程
2. **批量处理**：对于短时间内的多个UDP数据，进行批量推送
3. **连接池**：使用HttpClient连接池，减少连接建立开销

### 3.3 错误处理优化

1. **重试机制**：推送失败时进行有限次数的重试
2. **降级策略**：当Redis不可用时，直接从数据库获取数据
3. **监控告警**：对推送失败的情况进行监控和告警

## 4. 部署与配置

### 4.1 配置文件设置

在Web.config中添加必要的配置：

```xml
<appSettings>
    <!-- 现有配置... -->
    
    <!-- 推送API配置 -->
    <add key="PushApiUrl" value="http://example.com/api/push" />
    <!-- 定时同步间隔（分钟） -->
    <add key="SyncIntervalMinutes" value="5" />
</appSettings>
```

### 4.2 依赖项

- Newtonsoft.Json
- FluentScheduler（用于定时任务）
- CSRedis（用于Redis操作）

## 5. 测试方案

### 5.1 功能测试

1. **定时同步测试**：验证定时任务是否正常执行，Redis中是否有数据
2. **UDP数据推送测试**：模拟UDP数据，验证是否能正确获取信息并推送
3. **边界情况测试**：测试Redis不可用、网络异常等情况

### 5.2 性能测试

1. **并发测试**：模拟多个UDP数据同时到达，测试推送性能
2. **响应时间测试**：测量从UDP数据到达至推送完成的时间
3. **负载测试**：测试系统在高负载下的稳定性

## 6. 总结

本方案通过以下步骤实现了UDP数据的高效推送：

1. **定时同步**：定期从数据库获取主机信息并更新到Redis
2. **快速获取**：UDP数据到达时，从Redis快速获取所需信息
3. **异步推送**：使用异步方式将数据推送到外部接口
4. **容错处理**：当Redis不可用时，从数据库获取数据作为降级方案

该方案显著减少了数据库查询开销，提高了UDP数据处理和推送的效率，适用于高并发场景下的实时数据推送需求。