327 lines
11 KiB
Markdown
327 lines
11 KiB
Markdown
|
|
# 物联网系统网页访问性能问题分析报告
|
|||
|
|
|
|||
|
|
## 1. 问题概述
|
|||
|
|
|
|||
|
|
本项目在用户量较大时,IIS应用程序池频繁重启,主要原因与网页访问功能使用的复杂存储过程查询有关。系统采用SQLServer存储过程进行后台数据查询,在高并发场景下性能急剧下降,导致应用程序池崩溃重启。
|
|||
|
|
|
|||
|
|
## 2. 核心问题分析
|
|||
|
|
|
|||
|
|
### 2.1 存储过程使用情况
|
|||
|
|
|
|||
|
|
系统在以下关键功能中使用了复杂存储过程:
|
|||
|
|
|
|||
|
|
1. **回路状态记录查询**:`QueryHostModalRecords`
|
|||
|
|
2. **回路异常记录查询**:`QueryHostAbnormalRecords`
|
|||
|
|
3. **能源统计查询**:`QueryEnergyStatistics`
|
|||
|
|
4. **房态查询**:`QueryRoomRentState`、`QueryRoomState`等
|
|||
|
|
|
|||
|
|
### 2.2 性能瓶颈分析
|
|||
|
|
|
|||
|
|
通过代码分析,发现以下性能瓶颈:
|
|||
|
|
|
|||
|
|
1. **无缓存机制**:所有查询直接访问数据库,没有使用Redis或内存缓存
|
|||
|
|
2. **同步处理**:所有请求采用同步方式处理,没有实现异步操作
|
|||
|
|
3. **资源管理不当**:数据库连接管理不规范,可能导致连接泄漏
|
|||
|
|
4. **缺乏请求限流**:没有实现请求频率限制,高并发时系统压力过大
|
|||
|
|
5. **存储过程复杂度**:存储过程逻辑复杂,可能包含大量数据处理和计算
|
|||
|
|
6. **分页参数固定**:如`LoadHostModalRecords`方法中固定使用`page=1, rows=100`,可能查询过多数据
|
|||
|
|
|
|||
|
|
### 2.3 代码问题分析
|
|||
|
|
|
|||
|
|
1. **OverviewController.cs**:
|
|||
|
|
- 方法`LoadHostModalRecords`固定使用`page=1, rows=100`,忽略了传入的分页参数
|
|||
|
|
- 代码中存在"严重怀疑这里的调用有问题"的注释,表明开发人员已意识到问题
|
|||
|
|
|
|||
|
|
2. **OverviewRepository.cs**:
|
|||
|
|
- 所有数据库操作采用同步方式
|
|||
|
|
- 没有实现查询结果缓存
|
|||
|
|
- 资源管理不规范,如数据库连接的处理
|
|||
|
|
|
|||
|
|
3. **Global.asax.cs**:
|
|||
|
|
- 虽然实现了全局异常捕获,但没有针对性能问题的特殊处理
|
|||
|
|
- 没有实现请求限流机制
|
|||
|
|
|
|||
|
|
## 3. 优化建议
|
|||
|
|
|
|||
|
|
### 3.1 存储过程优化
|
|||
|
|
|
|||
|
|
1. **存储过程重构**:
|
|||
|
|
- 简化存储过程逻辑,将复杂计算移至应用层
|
|||
|
|
- 添加适当的索引,优化查询性能
|
|||
|
|
- 实现存储过程的参数化,避免SQL注入风险
|
|||
|
|
|
|||
|
|
2. **查询优化**:
|
|||
|
|
- 合理使用分页,避免一次性查询大量数据
|
|||
|
|
- 添加查询条件过滤,减少返回数据量
|
|||
|
|
- 优化JOIN操作,减少表关联复杂度
|
|||
|
|
|
|||
|
|
### 3.2 缓存机制引入
|
|||
|
|
|
|||
|
|
1. **Redis缓存**:
|
|||
|
|
- 对高频查询结果进行缓存,如房态信息、设备状态等
|
|||
|
|
- 设置合理的缓存过期时间,确保数据及时性
|
|||
|
|
- 实现缓存预热,系统启动时加载常用数据
|
|||
|
|
|
|||
|
|
2. **内存缓存**:
|
|||
|
|
- 对短期高频访问的数据使用MemoryCache
|
|||
|
|
- 实现缓存依赖,确保数据一致性
|
|||
|
|
|
|||
|
|
### 3.3 异步处理
|
|||
|
|
|
|||
|
|
1. **异步API实现**:
|
|||
|
|
- 将控制器方法改为异步方法,使用`async/await`
|
|||
|
|
- 实现异步数据库操作,减少线程阻塞
|
|||
|
|
|
|||
|
|
2. **并行处理**:
|
|||
|
|
- 对独立的查询操作使用并行处理,提高响应速度
|
|||
|
|
- 合理控制并发度,避免系统资源耗尽
|
|||
|
|
|
|||
|
|
### 3.4 资源管理优化
|
|||
|
|
|
|||
|
|
1. **数据库连接管理**:
|
|||
|
|
- 使用连接池,避免频繁创建和销毁连接
|
|||
|
|
- 确保连接正确关闭,避免连接泄漏
|
|||
|
|
|
|||
|
|
2. **内存管理**:
|
|||
|
|
- 减少大对象分配,避免GC压力
|
|||
|
|
- 使用对象池,重用频繁创建的对象
|
|||
|
|
|
|||
|
|
### 3.5 请求限流与保护
|
|||
|
|
|
|||
|
|
1. **请求限流**:
|
|||
|
|
- 实现基于IP的请求频率限制
|
|||
|
|
- 对高频API添加令牌桶限流
|
|||
|
|
|
|||
|
|
2. **熔断机制**:
|
|||
|
|
- 实现服务熔断,避免级联失败
|
|||
|
|
- 添加超时设置,防止请求长时间阻塞
|
|||
|
|
|
|||
|
|
3. **监控告警**:
|
|||
|
|
- 增加性能监控,实时跟踪系统状态
|
|||
|
|
- 设置告警阈值,及时发现性能异常
|
|||
|
|
|
|||
|
|
### 3.6 架构优化
|
|||
|
|
|
|||
|
|
1. **微服务拆分**:
|
|||
|
|
- 将报表查询等重负载功能拆分为独立服务
|
|||
|
|
- 减少服务间耦合,提高系统可扩展性
|
|||
|
|
|
|||
|
|
2. **读写分离**:
|
|||
|
|
- 实现数据库读写分离,减轻主库压力
|
|||
|
|
- 报表查询等操作定向到从库
|
|||
|
|
|
|||
|
|
3. **消息队列**:
|
|||
|
|
- 使用消息队列处理异步任务,如统计计算等
|
|||
|
|
- 提高系统的可靠性和弹性
|
|||
|
|
|
|||
|
|
## 4. 具体优化方案
|
|||
|
|
|
|||
|
|
### 4.1 紧急优化措施(短期)
|
|||
|
|
|
|||
|
|
1. **添加缓存机制**:
|
|||
|
|
- 对房态查询结果使用Redis缓存,过期时间5分钟
|
|||
|
|
- 对设备状态查询结果使用内存缓存,过期时间30秒
|
|||
|
|
|
|||
|
|
2. **优化存储过程调用**:
|
|||
|
|
- 修正`LoadHostModalRecords`方法,使用正确的分页参数
|
|||
|
|
- 添加参数验证,避免无效查询
|
|||
|
|
|
|||
|
|
3. **实现请求限流**:
|
|||
|
|
- 在`Application_BeginRequest`中添加请求频率限制
|
|||
|
|
- 对高频API如`LoadHostModalRecords`设置更严格的限制
|
|||
|
|
|
|||
|
|
### 4.2 中期优化措施
|
|||
|
|
|
|||
|
|
1. **存储过程重构**:
|
|||
|
|
- 简化`QueryHostModalRecords`存储过程逻辑
|
|||
|
|
- 添加适当的索引和查询优化
|
|||
|
|
|
|||
|
|
2. **异步处理实现**:
|
|||
|
|
- 将控制器方法改为异步方法
|
|||
|
|
- 实现异步数据库操作
|
|||
|
|
|
|||
|
|
3. **缓存策略完善**:
|
|||
|
|
- 设计合理的缓存键结构
|
|||
|
|
- 实现缓存预热和缓存更新机制
|
|||
|
|
|
|||
|
|
### 4.3 长期优化措施
|
|||
|
|
|
|||
|
|
1. **微服务架构改造**:
|
|||
|
|
- 将报表功能拆分为独立微服务
|
|||
|
|
- 实现服务发现和负载均衡
|
|||
|
|
|
|||
|
|
2. **数据库架构优化**:
|
|||
|
|
- 实现读写分离
|
|||
|
|
- 考虑使用时序数据库存储历史数据
|
|||
|
|
|
|||
|
|
3. **监控系统完善**:
|
|||
|
|
- 实现分布式追踪
|
|||
|
|
- 建立性能基准和告警机制
|
|||
|
|
|
|||
|
|
## 5. 代码优化建议
|
|||
|
|
|
|||
|
|
### 5.1 OverviewController.cs 优化
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 优化前
|
|||
|
|
public ActionResult LoadHostModalRecords(int? page, int? rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds)
|
|||
|
|
{
|
|||
|
|
long total = 0;
|
|||
|
|
var list = OverviewManager.LoadHostModalRecords(out total, 1, 100, order, sort, roomNumber, startTime, endTime, deviceType, modalIds, CurrentHotelID);
|
|||
|
|
var result = new { total = total, rows = list };
|
|||
|
|
return Content(JsonConvert.SerializeObject(result, new DataTableConverter()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优化后
|
|||
|
|
public async Task<ActionResult> LoadHostModalRecordsAsync(int? page, int? rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds)
|
|||
|
|
{
|
|||
|
|
// 验证分页参数
|
|||
|
|
int pageNum = page ?? 1;
|
|||
|
|
int pageSize = rows ?? 20;
|
|||
|
|
if (pageSize > 100) pageSize = 100; // 限制最大页大小
|
|||
|
|
|
|||
|
|
// 生成缓存键
|
|||
|
|
string cacheKey = $"HostModalRecords_{CurrentHotelID}_{pageNum}_{pageSize}_{order}_{sort}_{roomNumber}_{startTime}_{endTime}_{deviceType?.ToString()}_{modalIds}";
|
|||
|
|
|
|||
|
|
// 尝试从缓存获取
|
|||
|
|
var cachedResult = CSRedisCacheHelper.Get<object>(cacheKey);
|
|||
|
|
if (cachedResult != null)
|
|||
|
|
{
|
|||
|
|
return Json(cachedResult, JsonRequestBehavior.AllowGet);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 异步查询数据
|
|||
|
|
long total = 0;
|
|||
|
|
var list = await Task.Run(() =>
|
|||
|
|
OverviewManager.LoadHostModalRecords(out total, pageNum, pageSize, order, sort, roomNumber, startTime, endTime, deviceType, modalIds, CurrentHotelID)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
var result = new { total = total, rows = list };
|
|||
|
|
|
|||
|
|
// 缓存结果,过期时间5分钟
|
|||
|
|
CSRedisCacheHelper.Set(cacheKey, result, 300);
|
|||
|
|
|
|||
|
|
return Content(JsonConvert.SerializeObject(result, new DataTableConverter()));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 OverviewRepository.cs 优化
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 优化前
|
|||
|
|
public DataTable LoadHostModalRecords(out long total, int page, int rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds, int hotelID)
|
|||
|
|
{
|
|||
|
|
total = 0;
|
|||
|
|
IDbCommand cmd = Session.Connection.CreateCommand();
|
|||
|
|
cmd.CommandText = "QueryHostModalRecords";
|
|||
|
|
cmd.CommandType = CommandType.StoredProcedure;
|
|||
|
|
// 参数设置...
|
|||
|
|
IDbDataAdapter da = new SqlDataAdapter(cmd as SqlCommand);
|
|||
|
|
DataSet ds = new DataSet();
|
|||
|
|
da.Fill(ds);
|
|||
|
|
total = ds.Tables[0].Rows.Count;
|
|||
|
|
return ds.Tables[0];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优化后
|
|||
|
|
public async Task<DataTable> LoadHostModalRecordsAsync(out long total, int page, int rows, string order, string sort, string roomNumber, string startTime, string endTime, DeviceType? deviceType, string modalIds, int hotelID)
|
|||
|
|
{
|
|||
|
|
total = 0;
|
|||
|
|
// 使用异步数据库操作
|
|||
|
|
using (var connection = new SqlConnection(connectionString))
|
|||
|
|
{
|
|||
|
|
await connection.OpenAsync();
|
|||
|
|
using (var command = connection.CreateCommand())
|
|||
|
|
{
|
|||
|
|
command.CommandText = "QueryHostModalRecords";
|
|||
|
|
command.CommandType = CommandType.StoredProcedure;
|
|||
|
|
// 参数设置...
|
|||
|
|
|
|||
|
|
using (var adapter = new SqlDataAdapter(command as SqlCommand))
|
|||
|
|
{
|
|||
|
|
DataSet ds = new DataSet();
|
|||
|
|
// 使用异步填充
|
|||
|
|
await Task.Run(() => adapter.Fill(ds));
|
|||
|
|
total = ds.Tables[0].Rows.Count;
|
|||
|
|
return ds.Tables[0];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 Global.asax.cs 优化
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 添加请求限流和缓存管理
|
|||
|
|
protected void Application_BeginRequest(object sender, EventArgs e)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var ip = Request.UserHostAddress;
|
|||
|
|
var url = Request.Url.AbsolutePath.ToLower();
|
|||
|
|
|
|||
|
|
// 定期清理恶意IP记录
|
|||
|
|
var nowUtc = DateTime.UtcNow;
|
|||
|
|
CleanupIpRecordIfNeeded(nowUtc);
|
|||
|
|
|
|||
|
|
// 检查请求频率
|
|||
|
|
if (IsHighFrequencyRequest(ip))
|
|||
|
|
{
|
|||
|
|
logger.Error("高频请求IP:" + ip + " URL:" + url);
|
|||
|
|
Response.StatusCode = 429; // Too Many Requests
|
|||
|
|
Response.Write("请求过于频繁,请稍后再试");
|
|||
|
|
Context.ApplicationInstance.CompleteRequest();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恶意路径检查...
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
logger.Error("请求处理出错:" + ex.Message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool IsHighFrequencyRequest(string ip)
|
|||
|
|
{
|
|||
|
|
var now = DateTime.UtcNow;
|
|||
|
|
var info = _ipRequests.GetOrAdd(ip, _ => new RequestInfo());
|
|||
|
|
|
|||
|
|
lock (info)
|
|||
|
|
{
|
|||
|
|
info.RequestCount++;
|
|||
|
|
info.LastRequest = now;
|
|||
|
|
|
|||
|
|
// 如果10秒内超过30次请求,认为是高频请求
|
|||
|
|
if (info.RequestCount > 30 && (now - info.FirstRequest).TotalSeconds < 10)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 超过统计窗口则重置计数窗口
|
|||
|
|
if ((now - info.FirstRequest).TotalSeconds >= 10)
|
|||
|
|
{
|
|||
|
|
info.FirstRequest = now;
|
|||
|
|
info.RequestCount = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 预期优化效果
|
|||
|
|
|
|||
|
|
通过实施上述优化方案,预计可以达到以下效果:
|
|||
|
|
|
|||
|
|
1. **响应时间提升**:高频查询响应时间从秒级降至毫秒级
|
|||
|
|
2. **并发能力提升**:系统并发处理能力提升5-10倍
|
|||
|
|
3. **稳定性增强**:IIS应用程序池不再频繁重启
|
|||
|
|
4. **资源利用率提高**:CPU和内存使用更加合理
|
|||
|
|
5. **用户体验改善**:页面加载速度显著提升
|
|||
|
|
|
|||
|
|
## 6. 结论
|
|||
|
|
|
|||
|
|
本项目网页访问性能问题的根本原因在于复杂存储过程的不合理使用和缺乏有效的缓存机制。通过实施上述优化方案,可以显著提高系统的性能和稳定性,解决IIS应用程序池频繁重启的问题。
|
|||
|
|
|
|||
|
|
建议按照"紧急优化措施→中期优化措施→长期优化措施"的顺序逐步实施,确保系统在优化过程中的稳定性。同时,建立完善的性能监控体系,及时发现和解决新出现的性能问题。
|