2

请查看这个 scotty 应用程序(直接取自2014 年的这个旧答案):

import Web.Scotty
import Database.MongoDB
import qualified Data.Text.Lazy as T
import Control.Monad.IO.Class

runQuery :: Pipe -> Query -> IO [Document]
runQuery pipe query = access pipe master "nutrition" (find query >>= rest) 

main = do
  pipe <- connect $ host "127.0.0.1"
  scotty 3000 $ do
    get "/" $ do
      res <- liftIO $ runQuery pipe (select [] "stock_foods")
      text $ T.pack $ show res

pipe您会看到如何在 Web 应用程序启动时只创建一次数据库连接 ( )。随后,成千上万甚至数百万的访问者将同时点击“/”路径并使用相同的连接 ( pipe) 从数据库中读取。

我对如何正确使用有疑问Database.MongoDB

  1. 这是设置的正确方法吗?而不是为每次访问“/”创建一个数据库连接。在后一种情况下,我们可以同时拥有数百万个连接。这是气馁吗?这种方法的优点和缺点是什么?
  2. 在上面的应用程序中,如果数据库连接由于某种原因丢失并且需要重新创建会发生什么?你会如何从中恢复过来?
  3. 使用该auth功能进行身份验证怎么样?该auth函数应该只在创建后调用一次pipe,还是应该在每次点击“/”时调用?
  4. 有人说我应该使用池(Data.Pool)。看起来这只会有助于限制同时使用同一数据库连接的访问​​者数量。但我为什么要这样做?MongoDB 连接是否没有对同时使用的内置支持?
4

3 回答 3

2
  1. 即使您为每个客户端创建连接,您也无法创建太多连接。您将达到 ulimit。一旦你点击了那个 ulimit,点击这个 ulimit 的客户端就会得到一个运行时错误。它没有意义的原因是因为 mongodb 服务器将花费太多时间轮询所有这些连接,并且它只会拥有与您的 db 服务器拥有的 CPU 一样多的有意义的工作人员。一个连接不是一个坏主意,因为 mongodb 旨在发送多个请求并等待响应。因此,它将利用您的 mongodb 可以拥有的尽可能多的资源,但只有一个限制 - 您只有一个用于写入的管道,如果它意外关闭,您将需要自己重新创建此管道。因此,拥有一个连接池更有意义。它不需要很大。我有一个应用程序来验证用户并给他们令牌。每秒有 2500 个并发用户,它只有 3-4 个并发连接到数据库。

以下是连接池为您带来的好处:

  • 如果您达到池连接限制,您将等待下一个可用连接并且不会出现运行时错误。因此,您的应用程序将稍等片刻,而不是拒绝您的客户。

  • Pool 将为您重新创建连接。您可以将池配置为关闭多余的连接并根据需要创建更多连接,直到达到特定限制。如果您在读取或写入时连接中断,那么您只需从池中获取另一个连接。如果您不将断开的连接返回到池,池将为您创建另一个连接。

    1. 如果数据库连接关闭,则:此连接上的 mongodb 侦听器将退出在您的终端上打印错误消息,您的应用程序将收到 IO 错误。为了处理此错误,您需要创建另一个连接并重试。在处理这种情况时,您会明白使用数据库池更容易。因为最终您对此的解决方案将非常类似于连接池。

    2. 作为打开连接的一部分,我会进行一次身份验证。如果您以后需要对另一个用户进行身份验证,您可以随时进行。

    3. 是的,mongodb 处理同时使用,但就像我说的它只给一个管道来写,它很快就变成了一个瓶颈。如果您创建的连接数至少与您的 mongodb 服务器可以提供处理它们的线程(CPU 计数)一样多,那么它们将全速运行。

如果我错过了什么,请随时要求澄清。谢谢你的问题。

于 2016-08-17T17:29:51.717 回答
1

你真正想要的是一个数据库连接池。看看这个其他答案的代码。

如果您的 MongoDB 服务器处于安全模式auth,您可以使用to而不是。withMongoDBPool

于 2016-08-14T15:13:35.637 回答
1

这是设置的正确方法吗?而不是为每次访问“/”创建一个数据库连接。在后一种情况下,我们可以同时拥有数百万个连接。这是气馁吗?这种方法的优点和缺点是什么?

您不想打开一个连接然后使用它。您使用的支持 Scotty 的 HTTP 服务器称为 Warp。Warp 具有多核、多绿线设计。您可以在所有线程之间共享相同的连接,因为Database.MongoDB直截了当地说连接是线程安全的,但是当一个线程被阻塞等待响应时会发生什么(MongoDB 协议遵循简单的请求-响应设计所有Web 服务中的线程将阻塞。这是不幸的。

相反,我们可以在每个请求上创建一个连接。这简单地解决了一个线程阻塞另一个线程的问题,但导致了它自己的问题。建立 TCP 连接的开销虽然不大,但也不为零。回想一下,每次我们想要打开或关闭套接字时,我们都必须从用户跳转到内核,等待内核更新其内部数据结构,然后再跳转回来(上下文切换)。我们还必须处理 TCP 握手和再见。我们还会在高负载下耗尽文件描述符或内存。

如果我们有一个介于两者之间的解决方案,那就太好了。解决方案应该是

  • 线程安全
  • 让我们限制连接的数量,这样我们就不会耗尽操作系统的有限资源
  • 快的
  • 在正常负载下跨线程共享连接
  • 当我们经历增加的负载时创建新的连接
  • 允许我们在减少负载的情况下删除连接时清理资源(例如关闭句柄)
  • 希望已经由其他生产系统编写并经过实战测试

资源池解决的正是这个问题。

有人说我应该使用一个池(Data.Pool)。看起来这只会有助于限制同时使用同一数据库连接的访问​​者数量。但我为什么要这样做?MongoDB 连接是否没有对同时使用的内置支持?

目前还不清楚你所说的同时使用是什么意思。我可以猜到一种解释:你的意思是 HTTP/2 之类的东西,它在协议中内置了流水线。

流水线标准图片 http://research.worksap.com/wp-content/uploads/2015/08/pipeline.png

上面我们看到客户端向服务器发出多个请求,而无需等待响应,然后客户端可以按某种顺序接收响应。(时间从上往下流。)这个MongoDB没有。这是一个相当复杂的协议设计,并不比仅仅要求您的客户使用连接池好多少。MongoDB 在这里并不孤单:简单的请求和响应设计是 Postgres、MySQL、SQL Server 和大多数其他数据库所采用的。

并且:在所有线程被阻塞并且您的用户只看到一个加载栏之前,连接池确实限制了您可以作为 Web 服务承担的负载。但是这个问题在三种场景(连接池、一个共享连接、每个请求一个连接)中的任何一个都会存在!计算机的资源是有限的,在某些时候,在足够的负载下某些东西会崩溃。连接池的优点是它可以优雅地扩展直到它不能扩展。处理更多流量的正确解决方案是增加计算机的数量;我们不应该仅仅因为这个问题而避免池化。 

在上面的应用程序中,如果数据库连接由于某种原因丢失并且需要重新创建会发生什么?你会如何从中恢复过来?

我相信这些假设超出了 Stack Overflow 的范围,没有比“试试看”更好的答案了。Buuuuuuut 鉴于服务器终止了连接,我可以试一试可能发生的情况:假设 Warp 为每个请求分叉一个绿色线程(我认为它确实如此),每个线程IOException在尝试写入已关闭的线程时都会遇到未经检查的情况TCP 连接。Warp 会捕获这个异常并将其作为 HTTP 500 提供,希望也能写一些对日志有用的东西。假设你现在有一个单连接模型,你可以做一些聪明的(但代码行数很高)“重启”你的main功能并建立第二个连接。我为爱好项目做的事情:如果发生任何奇怪的事情,例如连接断开,我会要求我的主管进程(如 systemd)查看日志并重新启动 Web 服务。虽然对于制作、赚钱的网站来说显然不是一个很好的解决方案,但它对于小型应用程序来说已经足够好了。

使用该auth功能进行身份验证怎么样?该auth函数应该在创建管道后只调用一次,还是应该在每次点击“/”时调用它?

它应该在创建连接后调用一次。MongoDB 身份验证是按连接进行的。您可以在此处查看该db.auth()命令如何改变与当前客户端连接对应的 MongoDB 服务器数据结构的示例。

于 2016-08-16T18:22:18.867 回答