【问题标题】:What is the best method for making database connection (static, abstract, per request, ...)?建立数据库连接(静态,抽象,每个请求,...)的最佳方法是什么?
【发布时间】:2013-04-09 12:58:14
【问题描述】:

我使用了很多模型来连接数据库,在我使用 C# 和实体框架的上一个项目中,我创建了用于连接数据库的静态类,但是我在打开和关闭连接时遇到了问题,因为当超过10-15 个请求一起出现,我通过更改连接到 db 的方法解决了这个问题,我现在根据请求连接并删除了所有静态方法和类。

现在我想知道,

建立联系的最佳模型是什么?

  1. 我应该在每次查询后关闭它并在使用前打开它还是...?
  2. 静态类中的连接是很好的模型(我不需要 每次都创建)?
  3. 对于这个问题有好的设计模式吗?
  4. 所有这些都是针对同一个问题 什么是最好的方法 建立数据库连接(静态、抽象、每个请求……)?

例如我在一个sms sender web panel上工作,我应该每秒发送100K sms,这些sms与其他人一起收集并制作一个包,每个包有1~20个sms然后我需要每秒发送 5K~100K 个包裹,当我发送包裹时,我应该执行以下步骤:

  1. 将单个短信更新为已送达或未送达
  2. 如果交付减少用户帐户表中的用户余额,则更新用户余额
  3. 更新用户表中的短信发送计数
  4. 更新手机号码表中的短信发送次数
  5. 更新发件人号码表中的短信发送次数
  6. 更新包表中已发送和失败的短信的包
  7. 更新包以了解线程如何在包表中发送此包
  8. 更新线程表,了解该线程发送了多少条短信以及失败了多少条
  9. 在 AccountDocument 表中为此交易添加帐户文档

所有步骤和许多其他事情,如日志、用户界面和监控小部件,都应该这样做,我需要数据库连接来完成每一项事务。

现在,连接数据库的最佳模型是什么?通过人工请求或线程请求或每个事务..

【问题讨论】:

  • 查找 工作单元 和存储库模式,是开始的好地方。
  • 这取决于您希望如何使用事务,因为您通常希望为每个事务建立一个新连接(除非您可以确定事务不重叠)。您不希望每个事务有多个连接,因为您需要使用会损害性能的分布式事务。如果您没有从交易的角度考虑这一点,您可能应该这样做。
  • 选项“2”是绝对禁止的。永远不要这样做。
  • @Mehdi - 工作单元是要走的路。我的观点是不要考虑请求/静态/实例/线程。思考工作单元 = 事务。您的数据库访问模式不应与您需要执行的工作单元/事务以外的任何其他内容相关联。这是获得最佳性能和事务安全性的方法。 Google Unit Of Work,有很多不同实现的代码示例。
  • @MehdiYeganeh TBQH 如果您每秒要处理那么多请求,请雇用您能找到的最好的 DBA,他曾解决过亚马逊规模问题,因为这是一个 1-2 百万美元的问题。

标签: c# entity-framework design-patterns


【解决方案1】:

回答您的问题:

  1. 关闭它。 .NET 在后台为您提供连接池。

  2. 创建它。每次都使用 using (Connection conn = new ....) - 这样,您将充分利用 .NET 池机制。

  3. 您可以使用 .NET 线程池(或您自己的自定义线程池),将线程池定义为仅并行使用 10 个线程,并将工作项一个接一个地入队。这样,同一时间不会使用超过 10 个连接 + 它可能会更快地工作。 更多关于自定义线程池:Custom ThreadPool Implementation

  4. 每个实例。


这是我对架构的建议:

  1. 为待发送的 SMS 创建一个数据库表(队列)。

  2. 每一行将包含短信所需的所有信息+当前状态。

  3. 创建一个工作进程,也许是一个 Windows 服务,它将不断地对该表进行采样 - 比如说,每 5 秒。它将选择 TOP ~20 条状态为“待发送”的短信(应表示为 int)。并将状态更新为“发送中”

  4. 每条短信都将使用 Windows 服务端的自定义线程池发送出去。

  5. 在进程结束时,所有已处理的短信状态都将使用 CTE 更新为“完成”(通用表表达式 - 您可以发送一个包含刚刚处理的所有短信行 ID 的 cte对“完成”状态进行“批量更新”)。

  6. 您可以使状态更新存储过程与“getpending”相同。这样一来,您就可以无锁选择更新,让数据库工作得更快。

  7. 这样,您可以运行多个处理器服务(但随后您将不得不解除锁定)。

记住要尽可能避免锁定。

顺便说一句,这也很好,因为您可以从系统中的任何位置发送 SMS,只需在待处理 SMS 表中添加一行即可。

还有一件事,我不建议为此使用实体框架,因为它在幕后发生了太多事情。完成此类任务所需的只是调用 3-4 个存储过程,仅此而已。也许看看Dapper-dot-NET - 它是一个非常轻量级的 MicroDal 框架,在大多数情况下,它的运行速度比 EF(实体框架)快 10 倍以上

【讨论】:

  • 你能给我推荐一个使用自定义游泳池的好的设计模式吗?
【解决方案2】:

1. Should i close it after every query?

.Net 会为您执行此操作,所以让它处理它,这是一个垃圾收集器任务。所以不要费心手动处理你的对象,这是 Jon Skeet 的一个很好的答案:https://stackoverflow.com/a/1998600/544283。但是,您可以使用using(IDisposable){ } 语句来强制GC 完成它的工作。这是一篇关于资源重新分配的好文章:http://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

2. A connection in static class is good?

永远不要将数据上下文设为静态!数据上下文不是线程安全或并发安全的。

3. Is there a good design pattern for this problem?

正如 Belogix 提到的依赖注入和工作单元模式很棒,实际上实体框架就是一个工作单元本身。不过,DI 和 UoW 有点被高估了,如果这是您第一次处理 IoC 容器,那么实施起来并不容易,如果您要走这条路,我会推荐 Ninject。另一件事是,如果您不打算运行测试,您实际上并不需要 DI,这些模式的美妙之处在于解耦,因此您可以毫不费力地进行测试和模拟。

简而言之:如果您要针对您的代码运行测试,请选择这些模式。如果没有,我将为您提供一个示例,说明如何在您想要的服务之间共享您的数据上下文。这是第四个问题的答案。

4. What is the best method for making database connection (static, per request)?

您的上下文服务:

public class FooContextService {
    private readonly FooContext _ctx;

    public FooContext Context { get { return _ctx; } }

    public FooContextService() {
        _ctx = new FooContext();
    }
}

其他服务:

public class UnicornService {
    private readonly FooContext _ctx;

    public UnicornService(FooContextService contextService) {
        if (contextService == null)
            throw new ArgumentNullException("contextService");

        _ctx = contextService.Context;
    }

    public ICollection<Unicorn> GetList() {
        return _ctx.Unicorns.ToList();
    }
}

public class DragonService {
    private readonly FooContext _ctx;

    public DragonService(FooContextService contextService) {
        if (contextService == null)
            throw new ArgumentNullException("contextService");

        _ctx = contextService.Context;
    }

    public ICollection<Dragon> GetList() {
        return _ctx.Dragons.ToList();
    }
}

控制器:

public class FantasyController : Controller {
    private readonly FooContextService _contextService = new FooContextService();

    private readonly UnicornService _unicornService;
    private readonly DragonService _dragonService;

    public FantasyController() {
        _unicornService = new UnicornService(_contextService);
        _dragonService = new DragonService(_contextService);
    }

    // Controller actions
}

第二个想法(几乎是一个编辑): 如果您需要您的上下文不为您的实体创建代理,因此也没有延迟加载,您可以重载您的上下文服务,如下所示:

public class FooContextService {
    private readonly FooContext _ctx;

    public FooContext Context { get { return _ctx; } }

    public FooContextService() : this(true) { }

    public FooContextService(bool proxyCreationEnabled) {
        _ctx = new FooContext();
        _ctx.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
    }
}

注意:

  • 如果您将启用代理创建设置为 false,您将不会开箱即用延迟加载。
  • 如果您有 api 控制器,您不想处理任何完整的对象图。

编辑:

先读一读:

完成这个:

(_context as IObjectContextAdapter).ObjectContext.Connection.Open();

这是一篇关于 Managing Connections and Transactions 的精彩文章。

Entity 框架通过 Connection 属性公开 EntityConnection。读作:public sealed class EntityConnection : DbConnection

管理连接的注意事项:(取自上一个链接)

  • 如果在操作之前对象上下文尚未打开,则对象上下文将打开连接。如果对象上下文在操作过程中打开了连接,它总是会在操作完成后关闭连接。
  • 如果手动打开连接,对象上下文不会关闭它。调用 CloseDispose 将关闭连接。
  • 如果对象上下文创建了连接,则在释放上下文时始终释放连接。
  • 在长期运行的对象上下文中,您必须确保在不再需要上下文时将其释放。

希望对你有帮助。

【讨论】:

  • 什么? “不要费心手动处理你的对象”是个坏建议。而且“使用using(IDisposable){ } 语句强制GC 完成它的工作”也是不准确的——using 语句不会强制GC 做任何事情。
  • @default.kramer: "Don't bother disposing your objects manually": 手动处理每个对象?即使您手动处理每个对象,GC 也不会自动执行任何操作,我只是说 GC 在如何以及何时释放资源方面非常聪明。还有"use the using{} statement to force the GC to do it's work":您是否暗示using 块内的资源在其作用域完成时不会对该资源调用Dispose 方法?如果是这样,为什么在使用using 语句时需要实现IDisposable?我会更改答案以反映这一点。
  • 您的前两句话听起来像“只要让您的数据库连接闲置,未处理,GC 最终会处理它。”也许这不是你的意思。我认为您误解了 GC 和IDisposable 之间的关系(或缺乏关系)。一个using 块调用Dispose; GC 调用终结器。一个常见的“安全网”是让 Finalizer 确保调用了 Dispose,但在行为良好的应用程序中,不需要此安全网 - 所有 IDisposables 都应该在没有任何 GC 参与的情况下被处置。
  • 我也不同意“如果你不打算运行测试,你就不需要 DI”。好的 OO 设计倾向于支持 DI(容器管理与否)。我将主要的好处描述为“可组合性”,它可以带来更多可维护的应用程序。可测试性当然是一个好处,但它更多的是良好设计的副作用。我不建议“如果你要编写测试,就这样做,否则就这样做。”
  • @Esteban tanx 帮了我很多。我想知道你的答案,它真的很好,又是 tanx。我知道一次性对象和垃圾收集器,但我认为让垃圾收集器关闭连接不安全让我检查示例.. 我有一些 linQ 查询,我尝试找到应该从队列短信等待表发送短信一个线程和线程应该更新记录状态并加载所有短信包和所有手机号码并计算费用,当我使用异步调用方法时,GC无法处理关闭它。并且所有连接同时保持打开状态。
【解决方案3】:

我认为每个请求的扩展性最好。使用线程安全的连接池,并使连接范围与工作单元一致。让负责事务行为和工作单元的服务检查连接,使用它,并在工作单元提交或回滚时将其返回到池中。

更新:

10-12 秒提交状态更新?你做错了什么。您所写的问题不足以提供合适的答案。

Daily NASDAQ volume 是 1.3B 事务,在一天 8 小时的工作中,每秒可处理约 45K 事务。您的交易量是纳斯达克交易量的 2 倍。如果你想用一台机器来做这件事,我会说纳斯达克正在使用不止一台服务器。

我还想知道您是否可以不使用 ACID 更新该状态。毕竟,Starbucks doesn't use two-phase commit。也许更好的解决方案是使用带有阻塞队列的生产者/消费者模式,以便在发送后尽可能更新这些状态。

【讨论】:

  • 我的项目有更多问题..它是一个短信发送器面板,我每秒发送 100,000 条短信,我发送的每条短信都应该是数据库中的更新状态,我有 100 个线程一起工作我使用异步方法发送短信,另一方面,用户跟踪发送短信。现在,当我使用每个请求时,我的 cpu 性能变高,我无法在 1 秒内发送所有内容,需要 10-12 秒。没问题我升级我的硬件,如果我知道最好的方法和安全的方法是根据请求连接!
  • 10-12 秒不提交一个状态更新,对于 100000 条发送记录,我想在一秒钟内发送 100000 条,我需要更多的 nasdaq,我想在一秒钟内发送 100K,它只发送我也更新我的手机号码表和发件人号码和用户发送计数和线程计算单个线程发送多少短信,我减少用户余额,我创建一个帐户文件交易所有这些都用于发送 1 条短信和许多其他业务......
  • 你在这些 cmets 中听起来过度紧张和语无伦次。我想我不能为你做更多了。投票结束。
  • 对不起,我认为这是我的英语而不是我的个性。我改变了我所有的班级来连接到 db 5 次,每次我读到它,我真的很想找到一个更好的方法我应该在下周六交付项目,我想,我在数据库连接方面遇到了大问题。请给我介绍一种连接数据库的模式。我开始阅读有关工作单元的内容,并且星巴克不使用两阶段提交,我认为它很好,但我想找到更多模式。
  • 您是在单个服务器上执行此操作吗?您是否分析过内存和 CPU 以查看机器是否能够处理此负载?负载均衡吗?太多的未知数。您的问题不仅仅是一个简单的模式问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-10
  • 2011-03-20
  • 2010-10-19
  • 2011-02-19
  • 1970-01-01
相关资源
最近更新 更多