c# - 检查状态生存时间是否未过期

标签 c# postgresql asp.net-core

我有一个简单的 ASP.NET Core webapi 可以管理一些设备。

public class Device : BaseModel
{
    public string DeviceNumber { get; set; }
    public int TypeId { get; set; }
    public bool IsOnline { get; set; }
    public int? StatusDuration { get; set; }
    public bool IsDeleted { get; set; }
}

PostgreSQL 中的所有数据存储。

当设备更改 IsOnline 时,StatusDuration 设置为 30 分钟,例如(不同设备可能不同)。

public async Task ChangeOnline(int id, bool isOnline, int statusDuration )
{
    var device = await _dbContext.Devices.SingleOrDefaultAsync(x => x.Id == id);  
    if (device.IsOnline != isOnline)
    {
        device.IsOnline = isOnline;
        device.StatusDuration = statusDuration;
        device.UpdateDateTime = DateTime.UtcNow;
        await _dbContext.SaveChangesAsync();
    }
}

然后我需要执行一些后台作业来检查所有设备

(device.UpdateDateTime.AddMinutes(30) <= DateTime.UtcNow) && device.IsOnline

并将IsOnline设置为false

我尝试过的:

var dateTime = DateTime.UtcNow;
while(true)
{
    var devices = await _dbContext.Devices.Where(x => x.IsOnline && x.UpdateDateTime.AddMinutes(x.StatusDuration) <= dateTime).ToListAsync;
    foreach (var device in devices)
    {
       device.IsOnline = false;
       device.UpdateDateTime = dateTime;
    }
    await _dbContext.SaveChangesAsync();
    Thread.Sleep(5000);
}

但是这个调用数据库每 5000ms 并检查很多记录。有没有更好的方法如何在一段时间后禁用设备?也许我应该获取所有数据,然后在第二次迭代中只获取那些具有最近到期日期或类似日期的记录。我相信已经存在一些用于此类操作的算法。

最佳答案

最有效的方法是实现如果不活动时间固定为 30 分钟,那么我要做的第一件事就是更改您的查询,以便我们直接与 UpdateDateTime 而不是针对该字段执行函数:

var expiry = DateTime.UtcNow.AddMinutes(-30);
while(true)
{
    var devices = await _dbContext.Devices.Where(x => x.IsOnline && x.UpdateDateTime < expiry).ToListAsync;
    foreach (var device in devices)
    {
       device.IsOnline = false;
       device.UpdateDateTime = dateTime;
    }
    await _dbContext.SaveChangesAsync();
    Thread.Sleep(5000);
}

如果您必须使用 StatusDuration 那么您当前的查询应该足够有效,但是另一种选择是更改 ChangeOnline 方法以设置到期时间,而不是持续时间,现在我们将您查询中效率最低的方面转移到写入过程,这将提高读取速度,因为我们读取的频率将远高于写入频率,这通常是最好的权衡。

public async Task ChangeOnline(int id, bool isOnline, int statusDuration )
{
    var device = await _dbContext.Devices.SingleOrDefaultAsync(x => x.Id == id);  
    if (device.IsOnline != isOnline)
    {
        device.IsOnline = isOnline;
        device.StatusDuration = statusDuration;
        device.UpdateDateTime = DateTime.UtcNow;
        device.ExpiryDateTime = DateTime.UtcNow.AddMinutes(statusDuration);
        await _dbContext.SaveChangesAsync();
    }
}

然后你只需要查找过期列,没有功能:

var now = DateTime.UtcNow;
while(true)
{
    var devices = await _dbContext.Devices
                                  .Where(x => x.IsOnline && x.ExpiryDateTime <= now)
                                  .Take(batchSize)
                                  .ToListAsync;
...

您还应该确保在 IsOnlineUpdateDateTime 上的 Devices 表上有一个索引(如果您这样做,则为 ExpiryDateTime )

CREATE INDEX IX_DeviceExpiryCheck
ON Devices (IsOnline, UpdateDateTime);

这是一个非常高效的数据库索引查询,所以我不提倡带回更多数据并将其缓存在本地,在许多用例中这并不是一个真正的选择,因为其他进程可能会调用您的 ChangeOnline 或以其他方式修改 IsOnlineUpdateDateTime,这样您的缓存结果集无论如何都需要定期刷新。

如果有大量设备(超过 100 个)可能会过期,那么更新语句可能会占用更多资源,并且如果一次进行太多更改可能会中断您的管道。如果这成为一个问题,您可以批量查找,以下是 IMO 对您的问题最有效的解决方案:

var now = DateTime.UtcNow;
int recordCount = 0;
int batchSize = 50;
while(true)
{
    do 
    {
        var devices = await _dbContext.Devices
                                      .Where(x => x.IsOnline && x.ExpiryDateTime <= now)
                                      .Take(batchSize)
                                      .ToListAsync;
        foreach (var device in devices)
        {
           device.IsOnline = false;
           device.UpdateDateTime = dateTime;
        }
        await _dbContext.SaveChangesAsync();
        Thread.Sleep(1000); // reduce the DTUs on the db by spreading the updates out a bit
    } while(recordCount == batchSize && recordCount > 0); // just in case you set batch size to zero ;)
    Thread.Sleep(5000);
}

最后要考虑的是将结帐频率更改为 1 或 5 分钟,而不是 5 秒。

这是一个会对性能产生巨大影响的业务决策,您真的需要在 5 秒内知道设备在过去 X 分钟内是否处于事件状态吗?如果它在 30 分钟内没有事件,那么 00:30:00 和 00:30:05 或 00:31:00 之间有什么区别?

I'm part of a team that manages a large number IoT devices we have a pattern similar to yours for monitoring what we call the heart beat. In the end we removed the concept of IsOnline from the database altogether and just use a field like the UpdateDateTime on its own, to reduce writes to the DB, the conceptual field of IsOnline is therefore mved to the business logic layer as a runtime comparison on the UpdateDateTime. Such a change is likely to substantial to your current business logic and queries but it's another option to consider.

关于c# - 检查状态生存时间是否未过期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58702317/

相关文章:

c# - 从 MVC 的 DependencyResolver 过渡到 AutofacWebApiDependencyResolver - .Current 在哪里?

c# - 如何生成总和为预定值的 N 个随机值?

database - PostgreSQL - 保护数据库和隐藏结构

.net - 如何在启动时使用 HangFire 创建类似 cron 的作业?

c# - ASP.NET-Core 嵌套 ASP TagHelper

c# - .NET Core 应用程序,在 Kestrel 上自托管 Razor Web 应用程序,能否在完整的 .NET 和引用 system.web 上运行?

c# - 使用 LINQ 搜索关键字

c# - 如何降低 if-else 语句中的圈复杂度

postgresql - 将转义的 Unicode 字符转换回 PostgreSQL 中的实际字符

perl - pg_dump 单个表——可以在循环中执行吗?