节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.38 KiB
Stardust
using NewLife;
using NewLife.Data;
using NewLife.Log;
using NewLife.Threading;
using Stardust.Data;

namespace Stardust.Server.Services;

/// <summary>在线服务</summary>
public class OnlineService : IHostedService
{
    #region 属性
    private TimerX _timer;
    private TimerX _timer2;
    private RegistryService _registryService;
    private readonly IServiceProvider _serviceProvider;
    private readonly StarServerSetting _setting;
    private readonly ITracer _tracer;
    #endregion

    #region 构造
    public OnlineService(IServiceProvider serviceProvider, StarServerSetting setting, ITracer tracer)
    {
        //_registryService = registryService;
        _serviceProvider = serviceProvider;
        _setting = setting;
        _tracer = tracer;
    }
    #endregion

    #region 方法
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _timer = new TimerX(CheckAppOnline, null, 30_000, 30_000) { Async = true };
        _timer2 = new TimerX(CheckHealth, null, 60_000, 60_000) { Async = true };

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _timer.TryDispose();
        _timer2.TryDispose();

        return Task.CompletedTask;
    }

    private void CheckAppOnline(Object state)
    {
        // 节点超时
        var sessionTimeout = _setting.SessionTimeout;
        if (sessionTimeout > 0)
        {
            using var span = _tracer?.NewSpan(nameof(CheckAppOnline));

            var rs2 = AppOnline.ClearExpire(TimeSpan.FromSeconds(sessionTimeout), TimeSpan.FromDays(2));
            if (rs2 != null)
            {
                foreach (var olt in rs2)
                {
                    var app = olt?.App;
                    if (app != null)
                    {
                        var msg = L.T("[{0}/{1}] logged in at {2}, last active at {3}", app, olt?.Client, olt.CreateTime, olt.UpdateTime);
                        app.WriteHistory(L.T("Timed out offline"), true, msg, olt.Version, olt.CreateIP, olt.Client);

                        CheckOffline(app, L.T("Timed out offline"));
                    }
                }
            }

            //var rs3 = ConfigOnline.ClearExpire(TimeSpan.FromDays(7));
        }

        // 注册中心
        {
            var rs = AppService.ClearExpire(TimeSpan.FromDays(2));
            var rs2 = AppConsume.ClearExpire(TimeSpan.FromSeconds(sessionTimeout));
        }
    }

    private async Task CheckHealth(Object state)
    {
        using var span = _tracer?.NewSpan(nameof(CheckHealth));

        _registryService ??= _serviceProvider.GetService<RegistryService>();

        var page = new PageParameter { PageSize = 1000 };
        while (true)
        {
            var list = AppService.Search(-1, -1, null, true, null, page);
            if (list.Count == 0) break;

            foreach (var svc in list)
            {
                if (!svc.Enable || svc.Address.IsNullOrEmpty()) continue;

                var service = svc.Service;
                var url = service?.HealthAddress;
                if (service == null || !service.HealthCheck || url.IsNullOrEmpty()) continue;

                await _registryService.HealthCheck(svc);
            }

            page.PageIndex++;
        }
    }

    public static void CheckOffline(App app, String reason)
    {
        // 下线告警
        if (app.AlarmOnOffline)
        {
            var webhook = RobotHelper.GetAlarm(app.Project, app.Category, app.WebHook);
            if (webhook.IsNullOrEmpty()) return;

            // 查找该节点还有没有其它实例在线
            var olts = AppOnline.FindAllByApp(app.Id);
            if (olts.Count == 0)
            {
                var msg = $"应用[{app.Name}]({app.DisplayName})已下线!{reason} IP={app.LastIP}";
                RobotHelper.SendAlarmWithRecord(app.Category, app.WebHook, "应用下线告警", msg, "应用下线", app.Name);
            }
        }
    }

    /// <summary>检查应用上线恢复。在应用登录或心跳时调用</summary>
    /// <param name="app">应用</param>
    public static void CheckOnline(App app)
    {
        if (app == null || !app.AlarmOnOffline) return;

        var webhook = RobotHelper.GetAlarm(app.Project, app.Category, app.WebHook);
        if (webhook.IsNullOrEmpty()) return;

        RobotHelper.RecoverAlarm("应用下线", app.Name, app.Category, webhook);
    }
    #endregion
}