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