Files
Web_CRICS_Server_VS2010_Prod/UDP数据推送与Redis缓存方案.md

11 KiB
Raw Permalink Blame History

UDP数据推送与Redis缓存方案

1. 需求分析

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

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

2. 解决方案设计

2.1 定时任务设计

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

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中添加获取所有主机信息的接口

/// <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获取所需信息并推送到外部接口

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类中添加新的缓存键常量

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 数据模型设计

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中注册定时任务

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中添加必要的配置

<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数据处理和推送的效率适用于高并发场景下的实时数据推送需求。