2881099 / IdleBus

IdleBus 空闲对象管理容器,有效组织对象重复利用,自动创建、销毁,解决【实例】过多且长时间占用的问题。
109 stars 26 forks source link

注册失败,请勿重复注册 #4

Open moxixuan opened 4 years ago

moxixuan commented 4 years ago

您好,我有如下代码, 我想先判断某个key是否已经注册, 如果没有的话,就查询一下数据库,然后注册, 这看起来没有问题的。

if (!idleBus.Exists($"TakeOut{companyCode}"))
{
    var db = idleBus.GetTocCenterSqlConnection();
    var dbConfig = db.QueryFirst<DbConfig>($"select top 1 * from tbDbConfig(nolock) where CompanyCode = '{companyCode}';");
    var conStr = string.Format(connectionStringTemplate, dbConfig.DbService, dbConfig.DbName, dbConfig.DbUser, dbConfig.DbPwd);
    idleBus.Register($"TakeOut{dbConfig.CompanyCode}", () => new SqlConnection(conStr));
}

但是当sql查询的速度比较慢,或者是并发请求的时候,或导致重复注册的问题 我暂时想到的解决思路是在外面加一个锁

lock (object)
{
    if (!idleBus.Exists($"TakeOut{companyCode}"))
    {
        var db = idleBus.GetTocCenterSqlConnection();
        var dbConfig = db.QueryFirst<DbConfig>($"select top 1 * from tbDbConfig(nolock) where CompanyCode = '{companyCode}';");
        var conStr = string.Format(connectionStringTemplate, dbConfig.DbService, dbConfig.DbName, dbConfig.DbUser, dbConfig.DbPwd);
        idleBus.Register($"TakeOut{dbConfig.CompanyCode}", () => new SqlConnection(conStr));
    }
}

这样的话,在不考虑分布的清空下,是有效的。

请问这样的情况有没有其他解决思路呢?

2881099 commented 4 years ago

用 TryRegister 呢?

2881099 commented 4 years ago

注册 SqlConnection 对象,会并发访问同一个 key 吗?

moxixuan commented 4 years ago

用 TryRegister 呢?

你的意思是注册失败的异常都不处理吗?

2881099 commented 4 years ago

用 TryRegister 呢?

你的意思是注册失败的异常都不处理吗?

是的,只注册一次

moxixuan commented 4 years ago

注册 SqlConnection 对象,会并发访问同一个 key 吗?

是的,有可能(虽然出现的次数不多),我观察生产环境的日志,有看到这个异常

TakeOutGD 注册失败,请勿重复注册 ReuqestId: c8ac5f66fe014848 at IdleBus2.InternalRegister(TKey key, Func1 create, Nullable1 idle, Boolean isThrow) at IdleBus2.Register(TKey key, Func1 create) at MeiYiJia.TakeOut.Mall.Dapper.IdleBusExtensions.GetSqlConnectionByCompanyCode(IdleBus1 idleBus, String connectionStringTemplate, String companyCode) in /src/src/MeiYiJia.TakeOut.Mall.EntityFrameworkCore/Dapper/IdleBusExtensions.cs:line 33 at MeiYiJia.TakeOut.Mall.Dapper.IdleBusExtensions.GetSqlConnectionByCompanyCode(IdleBus1 idleBus, String companyCode) in /src/src/MeiYiJia.TakeOut.Mall.EntityFrameworkCore/Dapper/IdleBusExtensions.cs:line 44 at MeiYiJia.TakeOut.Mall.Dapper.StoreDapperRepository.GetPlatformInfoAsync(String storeCode) in /src/src

moxixuan commented 4 years ago

用 TryRegister 呢?

你的意思是注册失败的异常都不处理吗?

是的,只注册一次

但是这边有可能是其他的原因引起的注册失败,我是不是要捕捉到具体的错误,根据不同的情况处理?

2881099 commented 4 years ago

你可能理解错意思了,IdleBus是把一个对象重复使用,如果会并发访问 key 对应的实例,是不适合用的。

简单点说,一个 SqlConnection 实例不支持并发访问。

2881099 commented 4 years ago

注册的对象,必须是支持单例才行。

moxixuan commented 4 years ago

你可能理解错意思了,IdleBus是把一个对象重复使用,如果会并发访问 key 对应的实例,是不适合用的。

简单点说,一个 SqlConnection 实例不支持并发访问。

我这边只是使用了static

public static SqlConnection GetSqlConnectionByCompanyCode(this IdleBus<SqlConnection> idleBus
    , string connectionStringTemplate
    , string companyCode)
{
    if (!idleBus.Exists($"TakeOut{companyCode}"))
    {
        var db = idleBus.GetTocCenterSqlConnection();
        var dbConfig = db.QueryFirst<DbConfig>($"select top 1 * from tbDbConfig(nolock) where CompanyCode = '{companyCode}';");
        var conStr = string.Format(connectionStringTemplate, dbConfig.DbService, dbConfig.DbName, dbConfig.DbUser, dbConfig.DbPwd);
        idleBus.Register($"TakeOut{dbConfig.CompanyCode}", () => new SqlConnection(conStr));
    }
    var conn = idleBus.Get($"TakeOut{companyCode}");
    if (conn.State != ConnectionState.Open)
    {
        conn.Open();
    }
    return conn;
}

如果这个注册了,确实会重复使用,但是在docker重启之后,会有大量的访问,这个时候可能会同时注册

所以这里应该要保证线程安全的单例。是这样吗?

2881099 commented 4 years ago

你需要延迟释放 SqlConnection 吗,个人觉得 IdleBus 不适合你的场景。上面已经解释了原因

moxixuan commented 4 years ago

你需要延迟释放 SqlConnection 吗,个人觉得 IdleBus 不适合你的场景。上面已经解释了原因

这边不需要延迟释放 SqlConnection 的,这个封装的方法,主要是判断一下IdleBus 是否有这个key,如果有的话,直接返回SqlConnection (这是大多数情况)但是在第一次注册的时候,有可能会同时注册的情况,我现在通过

public static SqlConnection GetSqlConnectionByCompanyCode(this IdleBus<SqlConnection> idleBus
    , string connectionStringTemplate
    , string companyCode)
{
    if (!idleBus.Exists($"TakeOut{companyCode}"))
    {
        lock (_syncRoot)
        {
            if (!idleBus.Exists($"TakeOut{companyCode}"))
            {
                var db = idleBus.GetTocCenterSqlConnection();
                var dbConfig = db.QueryFirst<DbConfig>($"select top 1 * from tbDbConfig(nolock) where CompanyCode = '{companyCode}';");
                var conStr = string.Format(connectionStringTemplate, dbConfig.DbService, dbConfig.DbName, dbConfig.DbUser, dbConfig.DbPwd);
                idleBus.Register($"TakeOut{dbConfig.CompanyCode}", () => new SqlConnection(conStr));
            }
        }
    }

    var conn = idleBus.Get($"TakeOut{companyCode}");
    if (conn.State != ConnectionState.Open)
    {
        conn.Open();
    }
    return conn;
}

这种方式处理了,这时有效的。