Neue Performance-Metriken in `EDMIService` hinzugefügt, inkl. Felder für Job-Dauer, langsame Queries und Timestamps. `ServiceStatusResponse` um diese Metriken erweitert. `DatatableJob` optimiert: `<DisallowConcurrentExecution>` hinzugefügt, Logging für langsame Queries (>10s) und Jobs (>30s) verbessert, sowie Gesamt-Dauer-Messung integriert. `Scheduler` nutzt nun dedizierte MSSQL-Instanz zur Entkopplung von Datenbankoperationen. Thread-Pool-Größe reduziert und gestaffeltes Starten von INIT-Jobs eingeführt. Connection-Strings in `WindowsService` angepasst: `ApplicationName` spezifisch gesetzt und `MaxPoolSize` erhöht. Allgemeine Logging-Verbesserungen für bessere Skalierbarkeit und Stabilität.
148 lines
6.3 KiB
VB.net
148 lines
6.3 KiB
VB.net
Imports System.Collections.Specialized
|
||
Imports System.Data.SqlClient
|
||
Imports DigitalData.Modules.Database
|
||
Imports DigitalData.Modules.Logging
|
||
Imports Quartz
|
||
Imports Quartz.Impl
|
||
Imports Quartz.Impl.Matchers
|
||
Imports Quartz.Logging
|
||
|
||
Public Class Scheduler
|
||
Inherits BaseClass
|
||
|
||
Private ReadOnly _Factory As StdSchedulerFactory
|
||
Private ReadOnly _SchedulerMSSQL As MSSQLServer ' ✅ Dedizierte Connection für alle Scheduler-Operationen
|
||
Private _Scheduler As IScheduler
|
||
Private ReadOnly _JobListener As JobListener
|
||
Private ReadOnly _GlobalState As GlobalState
|
||
|
||
Private ReadOnly _Props = New NameValueCollection From {
|
||
{"quartz.serializer.type", "binary"},
|
||
{"quartz.threadPool.maxConcurrency", 2} ' ← von 3 auf 2
|
||
}
|
||
|
||
Private Const JOB_GROUP As String = "DatatableJobs"
|
||
|
||
Public Sub New(pLogConfig As LogConfig, pMSSQL_Config As MSSQLServer, pTableStore As DataSet, pGlobalState As GlobalState)
|
||
MyBase.New(pLogConfig)
|
||
|
||
_Factory = New StdSchedulerFactory(_Props)
|
||
|
||
' ✅ Separater Connection-Pool ausschließlich für Scheduler-Jobs
|
||
' Application Name weicht ab → ADO.NET legt eigenständigen Pool an
|
||
' → Scheduler-Queries blockieren nie die WCF-Verbindungen des Service
|
||
Dim oBuilder As New SqlConnectionStringBuilder(pMSSQL_Config.CurrentConnectionString)
|
||
oBuilder.ApplicationName = "EDMIService-Scheduler"
|
||
oBuilder.MaxPoolSize = 2 ' ← NEU: begrenzt SQL Server-Last unter Volllast
|
||
_SchedulerMSSQL = New MSSQLServer(pLogConfig, oBuilder.ToString())
|
||
Logger.Info("Scheduler: dedicated MSSQLServer instance created | AppName=[{0}]", oBuilder.ApplicationName)
|
||
|
||
_JobListener = New JobListener(pLogConfig, _SchedulerMSSQL, pTableStore)
|
||
_GlobalState = pGlobalState
|
||
|
||
_GlobalState.JobListener = _JobListener
|
||
Logger.Debug("JobListener registered in GlobalState")
|
||
|
||
Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger))
|
||
End Sub
|
||
|
||
Public Async Sub Start()
|
||
_Scheduler = Await _Factory.GetScheduler()
|
||
|
||
_Scheduler.ListenerManager.AddJobListener(_JobListener,
|
||
GroupMatcher(Of JobKey).GroupEquals(JOB_GROUP))
|
||
|
||
Await _Scheduler.Start()
|
||
|
||
Dim oCronjobs As DataTable = Await GetCronJobs()
|
||
|
||
Try
|
||
If oCronjobs IsNot Nothing Then
|
||
Logger.Debug("Loaded {0} cron jobs", oCronjobs.Rows.Count)
|
||
|
||
For Each oRow As DataRow In oCronjobs.Rows
|
||
Dim oDefinition As String = oRow.Item("CRON_DEFINITION")
|
||
Dim oTitle As String = oRow.Item("TITLE")
|
||
Dim oGuid As Integer = oRow.Item("GUID")
|
||
Logger.Debug("Trying to schedule a new job forr Cron Job [{0}] ...", oTitle)
|
||
Dim oCronTrigger As ITrigger
|
||
Dim oInitTrigger As ITrigger
|
||
|
||
Dim oJob As IJobDetail
|
||
Dim oInitJob As IJobDetail
|
||
|
||
Dim oJobData As New JobDataMap From {
|
||
{"LogConfig", LogConfig},
|
||
{"MSSQL", _SchedulerMSSQL},
|
||
{"CronJobId", oGuid},
|
||
{"CronJobTitle", oTitle}
|
||
}
|
||
' INIT-Job: gestaffelt starten – 15s Abstand statt 10s
|
||
' Bei maxConcurrency=2 laufen max. 2 Jobs gleichzeitig
|
||
Dim oInitDelaySeconds As Integer = oCronjobs.Rows.IndexOf(oRow) * 15
|
||
|
||
Dim oIdentity As String = $"CRON-JOB-{oGuid}"
|
||
|
||
oCronTrigger = TriggerBuilder.Create().
|
||
WithIdentity(oIdentity & "-TRIGGER", JOB_GROUP).
|
||
WithCronSchedule(oDefinition).
|
||
Build()
|
||
|
||
oJob = JobBuilder.Create(Of DatatableJob)().
|
||
WithIdentity(oIdentity, JOB_GROUP).
|
||
UsingJobData(oJobData).
|
||
Build()
|
||
|
||
Dim oNextFireTime As DateTimeOffset? = Await _Scheduler.ScheduleJob(oJob, oCronTrigger)
|
||
Logger.Info("Cron Job [{0}] -> next scheduled run: {1}", oTitle,
|
||
If(oNextFireTime.HasValue, oNextFireTime.Value.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"), "unknown"))
|
||
|
||
' Also create variant of the job that runs immediately, once
|
||
Dim oInitIdentity As String = $"CRON-JOB-INIT-{oGuid}"
|
||
|
||
oInitTrigger = TriggerBuilder.Create().
|
||
WithIdentity(oInitIdentity & "-TRIGGER", JOB_GROUP).
|
||
StartAt(DateTimeOffset.Now.AddSeconds(oInitDelaySeconds)). ' ✅ war: StartNow()
|
||
Build()
|
||
|
||
oInitJob = JobBuilder.Create(Of DatatableJob)().
|
||
WithIdentity(oInitIdentity, JOB_GROUP).
|
||
UsingJobData(oJobData).
|
||
Build()
|
||
|
||
Await _Scheduler.ScheduleJob(oInitJob, oInitTrigger)
|
||
|
||
Logger.Debug("Scheduled a new job for Cron Job [{0}]", oTitle)
|
||
Next
|
||
Logger.Info("✔ Scheduler started with {0} cron jobs", oCronjobs.Rows.Count)
|
||
Else
|
||
Logger.Warn("⚠ CronJobs could not be fetched!")
|
||
End If
|
||
Catch ex As Exception
|
||
Logger.Error(ex)
|
||
Logger.Warn("⚠ Unexpected Error while setting up scheduler: " & ex.Message)
|
||
End Try
|
||
End Sub
|
||
|
||
Public Async Function GetCronJobs() As Task(Of DataTable)
|
||
Try
|
||
Dim oSQL As String = "SELECT * FROM TBAPPSERV_CRON_JOB WITH (NOLOCK) WHERE ACTIVE = 1"
|
||
Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz
|
||
Catch ex As Exception
|
||
Return Nothing
|
||
End Try
|
||
End Function
|
||
|
||
Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable)
|
||
Try
|
||
Dim oSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WITH (NOLOCK) WHERE CRON_ID = {CronJobId}"
|
||
Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz
|
||
Catch ex As Exception
|
||
Return Nothing
|
||
End Try
|
||
End Function
|
||
|
||
Public Async Sub [Stop]()
|
||
Await _Scheduler.Shutdown()
|
||
End Sub
|
||
End Class |