Sic.Framework-Nanjing-Baishi/MECF.Framework.Common/Aitex/Core/RT/DataCollection/HighPerformance/DataRecorderDataTableCache.cs

151 lines
5.7 KiB
C#
Raw Normal View History

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
}
}