151 lines
5.7 KiB
C#
151 lines
5.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Aitex.Core.RT.DataCollection.HighPerformance
|
||
{
|
||
internal class DataRecorderDataTableCache : IDataRecorderCache
|
||
{
|
||
#region Variables
|
||
|
||
private readonly object _syncRoot = new();
|
||
private readonly int _maxRowsPerFlush;
|
||
private readonly string _tableName;
|
||
|
||
private DataTable _dataTable;
|
||
private readonly Queue<long> _qCachedTimestamps = new();
|
||
private readonly int _minCachePeriodMs;
|
||
private readonly Stopwatch _stopwatch = new();
|
||
|
||
private readonly StringBuilder _sqlExpr = new(5000000); // preallocate 5MB
|
||
private readonly StringBuilder _sqlValuesList = new(5000000); // preallocate 5MB
|
||
|
||
private int _lastPersistRows = 0;
|
||
private double _lastCachePeriodMs = 0;
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// 创建数据记录器缓存对象。
|
||
/// </summary>
|
||
/// <param name="tableName">当前Cache对应的数据库表名称。</param>
|
||
/// <param name="module">当前Cache对应的分组名称。</param>
|
||
/// <param name="dataHolders">数据获取器对象集合,保存当前Cache需要缓存的数据源。</param>
|
||
/// <param name="minCachePeriodMs">最小缓存周期,如果外部频繁调用<see cref="Cache"/>方法请求缓存数据,该周期内的重复请求被忽略,以避免高频缓存导致性能下降。</param>
|
||
/// <param name="maxRowsPerFlush">每次持久化的最大行数,避免一次性对数据库写入太多数据造成性能问题。</param>
|
||
/// <exception cref="ArgumentException"></exception>
|
||
public DataRecorderDataTableCache(string tableName, string module, IReadOnlyList<DataHolder> dataHolders, int minCachePeriodMs = 50, int maxRowsPerFlush = 2000)
|
||
{
|
||
Debug.Assert(dataHolders is { Count: > 0 }, "Incorrect Data Collector List.");
|
||
|
||
Module = module;
|
||
DataHolders = dataHolders;
|
||
_tableName = tableName;
|
||
_maxRowsPerFlush = maxRowsPerFlush;
|
||
_minCachePeriodMs = minCachePeriodMs;
|
||
|
||
// 必须传入有效的数据收集器列表。
|
||
if (dataHolders == null || dataHolders.Count == 0)
|
||
throw new ArgumentException("数据收集器列表不能为空。", nameof(dataHolders));
|
||
|
||
// 检查数据收集器列表中是否存在重复的序号。
|
||
if (dataHolders.GroupBy(dc => dc.Index).Any(g => g.Count() > 1))
|
||
throw new ArgumentException("数据收集器列表中存在重复的序号。", nameof(dataHolders));
|
||
|
||
// 检查数据收集器列表中是否存在重复的名称。
|
||
if (dataHolders.GroupBy(dc => dc.Name).Any(g => g.Count() > 1))
|
||
throw new ArgumentException("数据收集器列表中存在重复的名称。", nameof(dataHolders));
|
||
|
||
// 检查数据收集器列表中是否存在空的获取器。
|
||
if (dataHolders.Any(dc => dc.Read == null))
|
||
throw new ArgumentException("数据收集器列表中存在空的获取器。", nameof(dataHolders));
|
||
|
||
_dataTable = CreateDataTable();
|
||
|
||
|
||
|
||
/*DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.CachedCount", ()=> _qCachedTimestamps.Count, SubscriptionAttribute.FLAG.IgnoreSaveDB);
|
||
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistRows", ()=> _lastPersistRows, SubscriptionAttribute.FLAG.IgnoreSaveDB);
|
||
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistPeriodMs", ()=> _lastPersistPeriodMs, SubscriptionAttribute.FLAG.IgnoreSaveDB);*/
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// 返回当前缓存服务的模组名称。
|
||
/// </summary>
|
||
public string Module { get; }
|
||
|
||
/// <summary>
|
||
/// 返回数据获取器列表。
|
||
/// </summary>
|
||
public IReadOnlyList<DataHolder> DataHolders { get; }
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
/// <summary>
|
||
/// 创建临时数据表。
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private DataTable CreateDataTable()
|
||
{
|
||
lock (_syncRoot)
|
||
{
|
||
var dt = new DataTable(_tableName);
|
||
dt.Columns.Add("time", typeof(long));
|
||
foreach (var dh in DataHolders)
|
||
{
|
||
var ret = dh.Read();
|
||
dt.Columns.Add(dh.Name, ret.GetType());
|
||
}
|
||
|
||
return dt;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 立即缓存当前数据。
|
||
/// </summary>
|
||
public void Cache()
|
||
{
|
||
lock (_syncRoot)
|
||
{
|
||
// 限制最大Cache频率
|
||
if (_stopwatch.IsRunning && _stopwatch.ElapsedMilliseconds < _minCachePeriodMs)
|
||
return;
|
||
|
||
//TODO 需要限制Q的最大数据量,并且检测HalfFull和Full事件,并触发警告,可能PC性能出现问题
|
||
|
||
_lastCachePeriodMs = _stopwatch.ElapsedMilliseconds;
|
||
_stopwatch.Restart();
|
||
var ts = DateTime.Now.Ticks;
|
||
var row = _dataTable.NewRow();
|
||
row["time"] = ts;
|
||
var ret = Parallel.ForEach(DataHolders, dc =>
|
||
{
|
||
var ret = dc.Read();
|
||
row[dc.Name] = ret;
|
||
});
|
||
|
||
if (ret.IsCompleted)
|
||
{
|
||
_dataTable.Rows.Add(row);
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|